]> git.basschouten.com Git - openhab-addons.git/blob
3a125dd152467ad9b479cec1172dd5eda68edae6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.km200.internal.handler;
14
15 import static org.openhab.binding.km200.internal.KM200BindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.net.InetAddress;
19 import java.net.UnknownHostException;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.Set;
28 import java.util.concurrent.CopyOnWriteArrayList;
29 import java.util.concurrent.Executors;
30 import java.util.concurrent.ScheduledExecutorService;
31 import java.util.concurrent.TimeUnit;
32
33 import org.eclipse.jdt.annotation.NonNullByDefault;
34 import org.eclipse.jdt.annotation.Nullable;
35 import org.eclipse.jetty.client.HttpClient;
36 import org.openhab.binding.km200.internal.KM200Device;
37 import org.openhab.binding.km200.internal.KM200ServiceObject;
38 import org.openhab.binding.km200.internal.KM200ThingType;
39 import org.openhab.binding.km200.internal.KM200Utils;
40 import org.openhab.core.common.NamedThreadFactory;
41 import org.openhab.core.config.core.Configuration;
42 import org.openhab.core.library.types.DateTimeType;
43 import org.openhab.core.library.types.DecimalType;
44 import org.openhab.core.library.types.StringType;
45 import org.openhab.core.thing.Bridge;
46 import org.openhab.core.thing.Channel;
47 import org.openhab.core.thing.ChannelUID;
48 import org.openhab.core.thing.Thing;
49 import org.openhab.core.thing.ThingStatus;
50 import org.openhab.core.thing.ThingStatusDetail;
51 import org.openhab.core.thing.ThingStatusInfo;
52 import org.openhab.core.thing.ThingTypeUID;
53 import org.openhab.core.thing.binding.BaseBridgeHandler;
54 import org.openhab.core.types.Command;
55 import org.openhab.core.types.State;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 import com.google.gson.JsonObject;
60 import com.google.gson.JsonParseException;
61
62 /**
63  * The {@link KM200GatewayHandler} is responsible for handling commands, which are
64  * sent to one of the channels.
65  *
66  * @author Markus Eckhardt - Initial contribution
67  */
68 @NonNullByDefault
69 public class KM200GatewayHandler extends BaseBridgeHandler {
70
71     private final Logger logger = LoggerFactory.getLogger(KM200GatewayHandler.class);
72
73     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_KMDEVICE);
74
75     private final Map<Channel, JsonObject> sendMap = Collections.synchronizedMap(new LinkedHashMap<>());
76
77     private List<KM200GatewayStatusListener> listeners = new CopyOnWriteArrayList<>();
78
79     /**
80      * shared instance of HTTP client for (a)synchronous calls
81      */
82     private ScheduledExecutorService executor;
83     private final KM200Device remoteDevice;
84     private final KM200DataHandler dataHandler;
85     private int readDelay;
86     private int refreshInterval;
87
88     public KM200GatewayHandler(Bridge bridge, HttpClient httpClient) {
89         super(bridge);
90         refreshInterval = 120;
91         readDelay = 100;
92         updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.CONFIGURATION_PENDING);
93         remoteDevice = new KM200Device(httpClient);
94         dataHandler = new KM200DataHandler(remoteDevice);
95         executor = Executors.newScheduledThreadPool(2, new NamedThreadFactory("org.openhab.binding.km200", true));
96     }
97
98     @Override
99     public void handleCommand(ChannelUID channelUID, Command command) {
100         Channel channel = getThing().getChannel(channelUID.getId());
101         if (null != channel) {
102             if (command instanceof DateTimeType || command instanceof DecimalType || command instanceof StringType) {
103                 prepareMessage(thing, channel, command);
104             } else {
105                 logger.warn("Unsupported Command: {} Class: {}", command.toFullString(), command.getClass());
106             }
107         }
108     }
109
110     @Override
111     public void initialize() {
112         if (!getDevice().getInited()) {
113             logger.info("Update KM50/100/200 gateway configuration, it takes a minute....");
114             getConfiguration();
115             if (getDevice().isConfigured()) {
116                 if (!checkConfiguration()) {
117                     return;
118                 }
119                 /* configuration and communication seems to be ok */
120                 readCapabilities();
121                 updateStatus(ThingStatus.ONLINE);
122             } else {
123                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
124                 logger.debug("The KM50/100/200 gateway configuration is not complete");
125                 return;
126             }
127
128             SendKM200Runnable sendRunnable = new SendKM200Runnable(sendMap, getDevice());
129             GetKM200Runnable receivingRunnable = new GetKM200Runnable(sendMap, this, getDevice());
130             if (!executor.isTerminated()) {
131                 executor = Executors.newScheduledThreadPool(2,
132                         new NamedThreadFactory("org.openhab.binding.km200", true));
133                 executor.scheduleWithFixedDelay(receivingRunnable, 30, refreshInterval, TimeUnit.SECONDS);
134                 executor.scheduleWithFixedDelay(sendRunnable, 60, refreshInterval * 2, TimeUnit.SECONDS);
135             }
136         }
137     }
138
139     @Override
140     public void dispose() {
141         executor.shutdown();
142         try {
143             if (!executor.awaitTermination(60000, TimeUnit.SECONDS)) {
144                 logger.debug("Services didn't finish in 60000 seconds!");
145             }
146         } catch (InterruptedException e) {
147             executor.shutdownNow();
148         }
149         synchronized (getDevice()) {
150             getDevice().setInited(false);
151             getDevice().setIP4Address("");
152             getDevice().setCryptKeyPriv("");
153             getDevice().setMD5Salt("");
154             getDevice().setGatewayPassword("");
155             getDevice().setPrivatePassword("");
156             getDevice().serviceTreeMap.clear();
157         }
158         updateStatus(ThingStatus.OFFLINE);
159     }
160
161     @Override
162     public void handleRemoval() {
163         for (Thing actThing : getThing().getThings()) {
164             actThing.setStatusInfo(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, ""));
165         }
166         this.updateStatus(ThingStatus.REMOVED);
167     }
168
169     /**
170      * Gets bridges configuration
171      */
172     private void getConfiguration() {
173         Configuration configuration = getConfig();
174         for (String key : configuration.keySet()) {
175             logger.trace("initialize Key: {} Value: {}", key, configuration.get(key));
176             switch (key) {
177                 case "ip4Address":
178                     String ip = (String) configuration.get("ip4Address");
179                     if (ip != null && !ip.isBlank()) {
180                         try {
181                             InetAddress.getByName(ip);
182                         } catch (UnknownHostException e) {
183                             logger.warn("IP4_address is not valid!: {}", ip);
184                         }
185                         getDevice().setIP4Address(ip);
186                     } else {
187                         logger.debug("No ip4_address configured!");
188                     }
189                     break;
190                 case "privateKey":
191                     String privateKey = (String) configuration.get("privateKey");
192                     if (privateKey != null && !privateKey.isBlank()) {
193                         getDevice().setCryptKeyPriv(privateKey);
194                     }
195                     break;
196                 case "md5Salt":
197                     String md5Salt = (String) configuration.get("md5Salt");
198                     if (md5Salt != null && !md5Salt.isBlank()) {
199                         getDevice().setMD5Salt(md5Salt);
200                     }
201                     break;
202                 case "gatewayPassword":
203                     String gatewayPassword = (String) configuration.get("gatewayPassword");
204                     if (gatewayPassword != null && !gatewayPassword.isBlank()) {
205                         getDevice().setGatewayPassword(gatewayPassword);
206                     }
207                     break;
208                 case "privatePassword":
209                     String privatePassword = (String) configuration.get("privatePassword");
210                     if (privatePassword != null && !privatePassword.isBlank()) {
211                         getDevice().setPrivatePassword(privatePassword);
212                     }
213                     break;
214                 case "refreshInterval":
215                     refreshInterval = ((BigDecimal) configuration.get("refreshInterval")).intValue();
216                     logger.debug("Set refresh interval to: {} seconds.", refreshInterval);
217                     break;
218                 case "readDelay":
219                     readDelay = ((BigDecimal) configuration.get("readDelay")).intValue();
220                     logger.debug("Set read delay to: {} seconds.", readDelay);
221                     break;
222                 case "maxNbrRepeats":
223                     Integer maxNbrRepeats = ((BigDecimal) configuration.get("maxNbrRepeats")).intValue();
224                     logger.debug("Set max. number of repeats to: {} seconds.", maxNbrRepeats);
225                     remoteDevice.setMaxNbrRepeats(maxNbrRepeats);
226                     break;
227             }
228         }
229     }
230
231     /**
232      * Checks bridges configuration
233      */
234     private boolean checkConfiguration() {
235         /* Get HTTP Data from device */
236         JsonObject nodeRoot = remoteDevice.getServiceNode("/gateway/DateTime");
237         if (nodeRoot == null || nodeRoot.isJsonNull()) {
238             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
239                     "No communication possible with gateway");
240             return false;
241         }
242         logger.debug("Test of the communication to the gateway was successful..");
243
244         /* Testing the received data, is decryption working? */
245         try {
246             nodeRoot.get("type").getAsString();
247             nodeRoot.get("id").getAsString();
248         } catch (JsonParseException e) {
249             logger.debug("The data is not readable, check the key and password configuration! {}", e.getMessage());
250             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Wrong gateway configuration");
251             return false;
252         }
253         return true;
254     }
255
256     /**
257      * Reads the devices capabilities and sets the data structures
258      */
259     private void readCapabilities() {
260         KM200VirtualServiceHandler virtualServiceHandler;
261         /* Checking of the device specific services and creating of a service list */
262         for (KM200ThingType thing : KM200ThingType.values()) {
263             String rootPath = thing.getRootPath();
264             if (!rootPath.isEmpty() && (rootPath.indexOf("/", 0) == rootPath.lastIndexOf("/", rootPath.length() - 1))) {
265                 if (remoteDevice.getBlacklistMap().contains(thing.getRootPath())) {
266                     logger.debug("Service on blacklist: {}", thing.getRootPath());
267                     return;
268                 }
269                 KM200ServiceHandler serviceHandler = new KM200ServiceHandler(thing.getRootPath(), null, remoteDevice);
270                 serviceHandler.initObject();
271             }
272         }
273         /* Now init the virtual services */
274         virtualServiceHandler = new KM200VirtualServiceHandler(remoteDevice);
275         virtualServiceHandler.initVirtualObjects();
276         /* Output all available services in the log file */
277         getDevice().listAllServices();
278         updateBridgeProperties();
279         getDevice().setInited(true);
280     }
281
282     /**
283      * Adds a GatewayConnectedListener
284      */
285     public void addGatewayStatusListener(KM200GatewayStatusListener listener) {
286         listeners.add(listener);
287         listener.gatewayStatusChanged(getThing().getStatus());
288     }
289
290     /**
291      * Removes a GatewayConnectedListener
292      */
293     public void removeHubStatusListener(KM200GatewayStatusListener listener) {
294         listeners.remove(listener);
295     }
296
297     /**
298      * Refreshes a channel
299      */
300     public void refreshChannel(Channel channel) {
301         GetSingleKM200Runnable runnable = new GetSingleKM200Runnable(sendMap, this, getDevice(), channel);
302         logger.debug("starting single runnable.");
303         scheduler.submit(runnable);
304     }
305
306     /**
307      * Updates bridges properties
308      */
309     private void updateBridgeProperties() {
310         List<String> propertyServices = new ArrayList<>();
311         propertyServices.add(KM200ThingType.GATEWAY.getRootPath());
312         propertyServices.add(KM200ThingType.SYSTEM.getRootPath());
313         Map<String, String> bridgeProperties = editProperties();
314
315         for (KM200ThingType tType : KM200ThingType.values()) {
316             List<String> asProperties = tType.asBridgeProperties();
317             String rootPath = tType.getRootPath();
318             if (rootPath.isEmpty()) {
319                 continue;
320             }
321             KM200ServiceObject serObj = getDevice().getServiceObject(rootPath);
322             if (null != serObj) {
323                 for (String subKey : asProperties) {
324                     if (serObj.serviceTreeMap.containsKey(subKey)) {
325                         KM200ServiceObject subKeyObj = serObj.serviceTreeMap.get(subKey);
326                         String subKeyType = subKeyObj.getServiceType();
327                         if (!DATA_TYPE_STRING_VALUE.equals(subKeyType) && !DATA_TYPE_FLOAT_VALUE.equals(subKeyType)) {
328                             continue;
329                         }
330                         if (bridgeProperties.containsKey(subKey)) {
331                             bridgeProperties.remove(subKey);
332                         }
333                         Object value = subKeyObj.getValue();
334                         logger.trace("Add Property: {}  :{}", subKey, value);
335                         if (null != value) {
336                             bridgeProperties.put(subKey, value.toString());
337                         }
338                     }
339                 }
340             }
341         }
342         updateProperties(bridgeProperties);
343     }
344
345     /**
346      * Prepares a message for sending
347      */
348     public void prepareMessage(Thing thing, Channel channel, Command command) {
349         if (getDevice().getInited()) {
350             JsonObject newObject = null;
351             State state = null;
352             String service = KM200Utils.checkParameterReplacement(channel, getDevice());
353             String chTypes = channel.getAcceptedItemType();
354             if (null == chTypes) {
355                 logger.warn("Channel {} has not accepted item types", channel.getLabel());
356                 return;
357             }
358             logger.trace("handleCommand channel: {} service: {}", channel.getLabel(), service);
359             newObject = dataHandler.sendProvidersState(service, command, chTypes,
360                     KM200Utils.getChannelConfigurationStrings(channel));
361             synchronized (getDevice()) {
362                 KM200ServiceObject serObjekt = getDevice().getServiceObject(service);
363                 if (null != serObjekt) {
364                     if (newObject != null) {
365                         sendMap.put(channel, newObject);
366                     } else if (getDevice().containsService(service) && serObjekt.getVirtual() == 1) {
367                         String parent = serObjekt.getParent();
368                         for (Thing actThing : getThing().getThings()) {
369                             logger.trace("Checking: {}", actThing.getUID().getAsString());
370                             for (Channel tmpChannel : actThing.getChannels()) {
371                                 String tmpChTypes = tmpChannel.getAcceptedItemType();
372                                 if (null == tmpChTypes) {
373                                     logger.warn("Channel {} has not accepted item types", tmpChannel.getLabel());
374                                     return;
375                                 }
376                                 String actService = KM200Utils.checkParameterReplacement(tmpChannel, getDevice());
377                                 KM200ServiceObject actSerObjekt = getDevice().getServiceObject(actService);
378                                 if (null != actSerObjekt) {
379                                     String actParent = actSerObjekt.getParent();
380                                     if (actParent != null && actParent.equals(parent)) {
381                                         state = dataHandler.getProvidersState(actService, tmpChTypes,
382                                                 KM200Utils.getChannelConfigurationStrings(tmpChannel));
383                                         if (state != null) {
384                                             try {
385                                                 updateState(tmpChannel.getUID(), state);
386                                             } catch (IllegalStateException e) {
387                                                 logger.warn("Could not get updated item state", e);
388                                             }
389                                         }
390                                     }
391                                 }
392                             }
393                         }
394                     } else {
395                         logger.debug("Service is not availible: {}", service);
396                     }
397                 }
398             }
399         }
400     }
401
402     /**
403      * Update the children
404      */
405     // Every thing has here a handler
406     private void updateChildren(Map<Channel, JsonObject> sendMap, KM200GatewayHandler gatewayHandler,
407             KM200Device remoteDevice, @Nullable String parent) {
408         State state;
409         synchronized (remoteDevice) {
410             if (parent != null) {
411                 KM200ServiceObject serParObjekt = remoteDevice.getServiceObject(parent);
412                 if (null != serParObjekt) {
413                     serParObjekt.setUpdated(false);
414                 }
415             }
416             for (Thing actThing : gatewayHandler.getThing().getThings()) {
417                 for (Channel actChannel : actThing.getChannels()) {
418                     String actChTypes = actChannel.getAcceptedItemType();
419                     if (null == actChTypes) {
420                         logger.warn("Channel {} has not accepted item types", actChannel.getLabel());
421                         return;
422                     }
423                     logger.trace("Checking: {} Root: {}", actChannel.getUID().getAsString(),
424                             actChannel.getProperties().get("root"));
425                     KM200ThingHandler actHandler = (KM200ThingHandler) actThing.getHandler();
426                     if (actHandler != null) {
427                         if (!actHandler.checkLinked(actChannel)) {
428                             continue;
429                         }
430                     } else {
431                         continue;
432                     }
433                     String tmpService = KM200Utils.checkParameterReplacement(actChannel, remoteDevice);
434                     KM200ServiceObject tmpSerObjekt = remoteDevice.getServiceObject(tmpService);
435                     if (null != tmpSerObjekt) {
436                         if (parent == null || parent.equals(tmpSerObjekt.getParent())) {
437                             synchronized (sendMap) {
438                                 JsonObject obj = sendMap.get(actChannel);
439                                 if (obj != null) {
440                                     state = dataHandler.parseJSONData(obj, tmpSerObjekt.getServiceType(), tmpService,
441                                             actChTypes, KM200Utils.getChannelConfigurationStrings(actChannel));
442                                 } else {
443                                     state = dataHandler.getProvidersState(tmpService, actChTypes,
444                                             KM200Utils.getChannelConfigurationStrings(actChannel));
445                                 }
446                             }
447                             if (state != null) {
448                                 try {
449                                     gatewayHandler.updateState(actChannel.getUID(), state);
450                                 } catch (IllegalStateException e) {
451                                     logger.warn("Could not get updated item state", e);
452                                 }
453                             }
454                         }
455                         try {
456                             Thread.sleep(readDelay);
457                         } catch (InterruptedException e) {
458                             continue;
459                         }
460                     }
461                 }
462             }
463         }
464     }
465
466     /**
467      * Return the device instance.
468      */
469     public KM200Device getDevice() {
470         return remoteDevice;
471     }
472
473     /**
474      * The GetKM200Runnable class get the data from device to all items.
475      */
476     private class GetKM200Runnable implements Runnable {
477
478         private final KM200GatewayHandler gatewayHandler;
479         private final KM200Device remoteDevice;
480         private final Logger logger = LoggerFactory.getLogger(GetKM200Runnable.class);
481         private final Map<Channel, JsonObject> sendMap;
482
483         public GetKM200Runnable(Map<Channel, JsonObject> sendMap, KM200GatewayHandler gatewayHandler,
484                 KM200Device remoteDevice) {
485             this.sendMap = sendMap;
486             this.gatewayHandler = gatewayHandler;
487             this.remoteDevice = remoteDevice;
488         }
489
490         @Override
491         public void run() {
492             logger.debug("GetKM200Runnable");
493             synchronized (remoteDevice) {
494                 if (remoteDevice.getInited()) {
495                     remoteDevice.resetAllUpdates(remoteDevice.serviceTreeMap);
496                     updateChildren(sendMap, gatewayHandler, remoteDevice, null);
497                 }
498             }
499         }
500     }
501
502     /**
503      * The GetKM200Runnable class get the data from device for one channel.
504      */
505     private class GetSingleKM200Runnable implements Runnable {
506
507         private final Logger logger = LoggerFactory.getLogger(GetSingleKM200Runnable.class);
508         private final KM200GatewayHandler gatewayHandler;
509         private final KM200Device remoteDevice;
510         private final Channel channel;
511         private final Map<Channel, JsonObject> sendMap;
512
513         public GetSingleKM200Runnable(Map<Channel, JsonObject> sendMap, KM200GatewayHandler gatewayHandler,
514                 KM200Device remoteDevice, Channel channel) {
515             this.gatewayHandler = gatewayHandler;
516             this.remoteDevice = remoteDevice;
517             this.channel = channel;
518             this.sendMap = sendMap;
519         }
520
521         @Override
522         public void run() {
523             logger.debug("GetKM200Runnable");
524             State state = null;
525             synchronized (remoteDevice) {
526                 synchronized (sendMap) {
527                     if (sendMap.containsKey(channel)) {
528                         return;
529                     }
530                 }
531                 if (remoteDevice.getInited()) {
532                     logger.trace("Checking: {} Root: {}", channel.getUID().getAsString(),
533                             channel.getProperties().get("root"));
534                     String chTypes = channel.getAcceptedItemType();
535                     if (null == chTypes) {
536                         logger.warn("Channel {} has not accepted item types", channel.getLabel());
537                         return;
538                     }
539                     String service = KM200Utils.checkParameterReplacement(channel, remoteDevice);
540                     KM200ServiceObject object = remoteDevice.getServiceObject(service);
541                     if (null != object) {
542                         if (object.getVirtual() == 1) {
543                             String parent = object.getParent();
544                             updateChildren(sendMap, gatewayHandler, remoteDevice, parent);
545                         } else {
546                             object.setUpdated(false);
547                             synchronized (sendMap) {
548                                 KM200ServiceObject serObjekt = remoteDevice.getServiceObject(service);
549                                 if (null != serObjekt) {
550                                     JsonObject obj = sendMap.get(channel);
551                                     if (obj != null) {
552                                         state = dataHandler.parseJSONData(obj, serObjekt.getServiceType(), service,
553                                                 chTypes, KM200Utils.getChannelConfigurationStrings(channel));
554                                     } else {
555                                         state = dataHandler.getProvidersState(service, chTypes,
556                                                 KM200Utils.getChannelConfigurationStrings(channel));
557                                     }
558                                 }
559                                 if (state != null) {
560                                     try {
561                                         gatewayHandler.updateState(channel.getUID(), state);
562                                     } catch (IllegalStateException e) {
563                                         logger.warn("Could not get updated item state", e);
564                                     }
565                                 }
566                             }
567                         }
568                     }
569                 }
570             }
571         }
572     }
573
574     /**
575      * The sendKM200Thread class sends the data to the device.
576      */
577     private class SendKM200Runnable implements Runnable {
578
579         private final Logger logger = LoggerFactory.getLogger(SendKM200Runnable.class);
580         private final Map<Channel, JsonObject> newObject;
581         private final KM200Device remoteDevice;
582
583         public SendKM200Runnable(Map<Channel, JsonObject> newObject, KM200Device remoteDevice) {
584             this.newObject = newObject;
585             this.remoteDevice = remoteDevice;
586         }
587
588         @Override
589         public void run() {
590             logger.debug("Send-Executor started");
591             Map.Entry<Channel, JsonObject> nextEntry;
592             /* Check whether a new entry is availible, if yes then take and remove it */
593             do {
594                 nextEntry = null;
595                 synchronized (remoteDevice) {
596                     synchronized (newObject) {
597                         Iterator<Entry<Channel, JsonObject>> i = newObject.entrySet().iterator();
598                         if (i.hasNext()) {
599                             nextEntry = i.next();
600                             i.remove();
601                         }
602                     }
603                     if (nextEntry != null) {
604                         /* Now send the data to the device */
605                         Channel channel = nextEntry.getKey();
606                         JsonObject newObject = nextEntry.getValue();
607
608                         String service = KM200Utils.checkParameterReplacement(channel, remoteDevice);
609                         KM200ServiceObject object = remoteDevice.getServiceObject(service);
610                         if (null != object) {
611                             if (object.getVirtual() == 0) {
612                                 remoteDevice.setServiceNode(service, newObject);
613                             } else {
614                                 String parent = object.getParent();
615                                 if (null != parent) {
616                                     logger.trace("Sending: {} to : {}", newObject, service);
617                                     remoteDevice.setServiceNode(parent, newObject);
618                                 }
619                             }
620                         }
621                     }
622                 }
623             } while (nextEntry != null);
624         }
625     }
626 }