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