2b351405b471bef7c1619eda01406cfe9c6e10ef
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / BindingCodecContext.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12 import static com.google.common.base.Verify.verify;
13 import static com.google.common.base.Verify.verifyNotNull;
14 import static java.util.Objects.requireNonNull;
15
16 import com.google.common.base.Strings;
17 import com.google.common.base.Throwables;
18 import com.google.common.cache.CacheBuilder;
19 import com.google.common.cache.CacheLoader;
20 import com.google.common.cache.LoadingCache;
21 import com.google.common.collect.ImmutableList;
22 import com.google.common.collect.ImmutableMap;
23 import com.google.common.util.concurrent.UncheckedExecutionException;
24 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
25 import java.io.File;
26 import java.io.IOException;
27 import java.lang.reflect.Method;
28 import java.lang.reflect.ParameterizedType;
29 import java.lang.reflect.Type;
30 import java.lang.reflect.WildcardType;
31 import java.time.Instant;
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Map.Entry;
38 import java.util.Optional;
39 import java.util.ServiceLoader;
40 import java.util.concurrent.ExecutionException;
41 import org.eclipse.jdt.annotation.NonNull;
42 import org.eclipse.jdt.annotation.Nullable;
43 import org.kohsuke.MetaInfServices;
44 import org.opendaylight.mdsal.binding.dom.codec.api.BindingAugmentationCodecTreeNode;
45 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeNode;
46 import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode;
47 import org.opendaylight.mdsal.binding.dom.codec.api.BindingInstanceIdentifierCodec;
48 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeWriterFactory;
49 import org.opendaylight.mdsal.binding.dom.codec.api.BindingStreamEventWriter;
50 import org.opendaylight.mdsal.binding.dom.codec.api.BindingYangDataCodecTreeNode;
51 import org.opendaylight.mdsal.binding.dom.codec.api.CommonDataObjectCodecTreeNode;
52 import org.opendaylight.mdsal.binding.dom.codec.api.IncorrectNestingException;
53 import org.opendaylight.mdsal.binding.dom.codec.api.MissingSchemaException;
54 import org.opendaylight.mdsal.binding.dom.codec.spi.AbstractBindingNormalizedNodeSerializer;
55 import org.opendaylight.mdsal.binding.dom.codec.spi.BindingDOMCodecServices;
56 import org.opendaylight.mdsal.binding.dom.codec.spi.BindingSchemaMapping;
57 import org.opendaylight.mdsal.binding.loader.BindingClassLoader;
58 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
59 import org.opendaylight.mdsal.binding.runtime.api.ActionRuntimeType;
60 import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeContext;
61 import org.opendaylight.mdsal.binding.runtime.api.ChoiceRuntimeType;
62 import org.opendaylight.mdsal.binding.runtime.api.ContainerLikeRuntimeType;
63 import org.opendaylight.mdsal.binding.runtime.api.ContainerRuntimeType;
64 import org.opendaylight.mdsal.binding.runtime.api.DataRuntimeType;
65 import org.opendaylight.mdsal.binding.runtime.api.InputRuntimeType;
66 import org.opendaylight.mdsal.binding.runtime.api.ListRuntimeType;
67 import org.opendaylight.mdsal.binding.runtime.api.NotificationRuntimeType;
68 import org.opendaylight.mdsal.binding.runtime.api.OutputRuntimeType;
69 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
70 import org.opendaylight.yangtools.concepts.Immutable;
71 import org.opendaylight.yangtools.util.ClassLoaderUtils;
72 import org.opendaylight.yangtools.yang.binding.Action;
73 import org.opendaylight.yangtools.yang.binding.Augmentation;
74 import org.opendaylight.yangtools.yang.binding.BaseIdentity;
75 import org.opendaylight.yangtools.yang.binding.BaseNotification;
76 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
77 import org.opendaylight.yangtools.yang.binding.DataContainer;
78 import org.opendaylight.yangtools.yang.binding.DataObject;
79 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
80 import org.opendaylight.yangtools.yang.binding.Key;
81 import org.opendaylight.yangtools.yang.binding.KeyAware;
82 import org.opendaylight.yangtools.yang.binding.KeyedListAction;
83 import org.opendaylight.yangtools.yang.binding.Notification;
84 import org.opendaylight.yangtools.yang.binding.OpaqueObject;
85 import org.opendaylight.yangtools.yang.binding.RpcInput;
86 import org.opendaylight.yangtools.yang.binding.RpcOutput;
87 import org.opendaylight.yangtools.yang.binding.YangData;
88 import org.opendaylight.yangtools.yang.common.QName;
89 import org.opendaylight.yangtools.yang.common.YangDataName;
90 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
91 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
92 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
93 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
94 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
95 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
96 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
97 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
98 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
99 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
100 import org.opendaylight.yangtools.yang.data.api.schema.ValueNode;
101 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
102 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
103 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
104 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
105 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
106 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
107 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
108 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
109 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
110 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
111 import org.opendaylight.yangtools.yang.model.api.TypeAware;
112 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
113 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
114 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
115 import org.opendaylight.yangtools.yang.model.api.stmt.PresenceEffectiveStatement;
116 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
117 import org.opendaylight.yangtools.yang.model.api.stmt.TypeDefinitionAware;
118 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
119 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
120 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
121 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
122 import org.slf4j.Logger;
123 import org.slf4j.LoggerFactory;
124
125 @MetaInfServices(value = BindingDOMCodecServices.class)
126 public final class BindingCodecContext extends AbstractBindingNormalizedNodeSerializer
127         implements BindingDOMCodecServices, Immutable, CodecContextFactory, DataContainerSerializerRegistry {
128     private static final Logger LOG = LoggerFactory.getLogger(BindingCodecContext.class);
129     private static final @NonNull NodeIdentifier FAKE_NODEID = new NodeIdentifier(QName.create("fake", "fake"));
130     private static final File BYTECODE_DIRECTORY;
131
132     static {
133         final String dir = System.getProperty("org.opendaylight.mdsal.binding.dom.codec.loader.bytecodeDumpDirectory");
134         BYTECODE_DIRECTORY = Strings.isNullOrEmpty(dir) ? null : new File(dir);
135     }
136
137     private final LoadingCache<Class<?>, DataContainerStreamer<?>> streamers = CacheBuilder.newBuilder()
138         .build(new CacheLoader<>() {
139             @Override
140             public DataContainerStreamer<?> load(final Class<?> key) throws ReflectiveOperationException {
141                 final var streamer = DataContainerStreamerGenerator.generateStreamer(loader, BindingCodecContext.this,
142                     key);
143                 final var instance = streamer.getDeclaredField(DataContainerStreamerGenerator.INSTANCE_FIELD);
144                 return (DataContainerStreamer<?>) instance.get(null);
145             }
146         });
147     private final LoadingCache<Class<?>, DataContainerSerializer> serializers = CacheBuilder.newBuilder()
148         .build(new CacheLoader<>() {
149             @Override
150             public DataContainerSerializer load(final Class<?> key) throws ExecutionException {
151                 return new DataContainerSerializer(BindingCodecContext.this, streamers.get(key));
152             }
153         });
154     private final LoadingCache<Class<? extends DataObject>, DataContainerCodecContext<?, ?, ?>> childrenByClass =
155         CacheBuilder.newBuilder().build(new CacheLoader<>() {
156             @Override
157             public DataContainerCodecContext<?, ?, ?> load(final Class<? extends DataObject> key) {
158                 final var childSchema = context.getTypes().bindingChild(JavaTypeName.create(key));
159                 if (childSchema instanceof ContainerLikeRuntimeType containerLike) {
160                     if (childSchema instanceof ContainerRuntimeType container
161                         && container.statement().findFirstEffectiveSubstatement(PresenceEffectiveStatement.class)
162                             .isEmpty()) {
163                         return new StructuralContainerCodecContext<>(key, container, BindingCodecContext.this);
164                     }
165                     return new ContainerLikeCodecContext<>(key, containerLike, BindingCodecContext.this);
166                 } else if (childSchema instanceof ListRuntimeType list) {
167                     return list.keyType() == null ? new ListCodecContext<>(key, list, BindingCodecContext.this)
168                         : MapCodecContext.of(key, list, BindingCodecContext.this);
169                 } else if (childSchema instanceof ChoiceRuntimeType choice) {
170                     return new ChoiceCodecContext<>(key.asSubclass(ChoiceIn.class), choice, BindingCodecContext.this);
171                 } else if (childSchema == null) {
172                     throw DataContainerCodecContext.childNullException(context, key, "%s is not top-level item.", key);
173                 } else {
174                     throw new IncorrectNestingException("%s is not a valid data tree child of %s", key, this);
175                 }
176             }
177         });
178
179     // FIXME: this could also be a leaf!
180     private final LoadingCache<QName, DataContainerCodecContext<?, ?, ?>> childrenByDomArg =
181         CacheBuilder.newBuilder().build(new CacheLoader<>() {
182             @Override
183             public DataContainerCodecContext<?, ?, ?> load(final QName qname) throws ClassNotFoundException {
184                 final var type = context.getTypes();
185                 final var child = type.schemaTreeChild(qname);
186                 if (child == null) {
187                     final var module = qname.getModule();
188                     if (context.modelContext().findModule(module).isEmpty()) {
189                         throw new MissingSchemaException(
190                             "Module " + module + " is not present in current schema context.");
191                     }
192                     throw new IncorrectNestingException("Argument %s is not valid child of %s", qname, type);
193                 }
194
195                 if (!(child instanceof DataRuntimeType)) {
196                     throw new IncorrectNestingException("Argument %s is not valid data tree child of %s", qname, type);
197                 }
198
199                 // TODO: improve this check?
200                 final var childSchema = child.statement();
201                 if (childSchema instanceof DataNodeContainer || childSchema instanceof ChoiceSchemaNode) {
202                     return getStreamChild(context.loadClass(child.javaType()));
203                 }
204
205                 throw new UnsupportedOperationException("Unsupported child type " + childSchema.getClass());
206             }
207         });
208
209     private final LoadingCache<Class<? extends DataObject>, ChoiceCodecContext<?>> choicesByClass =
210         CacheBuilder.newBuilder().build(new CacheLoader<>() {
211             @Override
212             public ChoiceCodecContext<?> load(final Class<? extends DataObject> caseType) {
213                 final var choiceClass = findCaseChoice(caseType);
214                 if (choiceClass == null) {
215                     throw new IllegalArgumentException(caseType + " is not a valid case representation");
216                 }
217                 if (context.getSchemaDefinition(choiceClass) instanceof ChoiceRuntimeType choiceType) {
218                     // FIXME: accurate type!
219                     return new ChoiceCodecContext(choiceClass, choiceType, BindingCodecContext.this);
220                 }
221                 throw new IllegalArgumentException(caseType + " does not refer to a choice");
222             }
223
224             private static Class<?> findCaseChoice(final Class<? extends DataObject> caseClass) {
225                 for (var type : caseClass.getGenericInterfaces()) {
226                     if (type instanceof Class<?> typeClass && ChoiceIn.class.isAssignableFrom(typeClass)) {
227                         return typeClass.asSubclass(ChoiceIn.class);
228                     }
229                 }
230                 return null;
231             }
232         });
233
234     private final LoadingCache<Class<? extends Action<?, ?, ?>>, ActionCodecContext> actionsByClass =
235         CacheBuilder.newBuilder().build(new CacheLoader<>() {
236             @Override
237             public ActionCodecContext load(final Class<? extends Action<?, ?, ?>> action) {
238                 if (KeyedListAction.class.isAssignableFrom(action)) {
239                     return prepareActionContext(2, 3, 4, action, KeyedListAction.class);
240                 } else if (Action.class.isAssignableFrom(action)) {
241                     return prepareActionContext(1, 2, 3, action, Action.class);
242                 }
243                 throw new IllegalArgumentException("The specific action type does not exist for action "
244                     + action.getName());
245             }
246
247             private ActionCodecContext prepareActionContext(final int inputOffset, final int outputOffset,
248                     final int expectedArgsLength, final Class<? extends Action<?, ?, ?>> action,
249                     final Class<?> actionType) {
250                 final var args = ClassLoaderUtils.findParameterizedType(action, actionType)
251                     .orElseThrow(() -> new IllegalStateException(action + " does not specialize " + actionType))
252                     .getActualTypeArguments();
253                 checkArgument(args.length == expectedArgsLength, "Unexpected (%s) Action generatic arguments",
254                     args.length);
255                 final ActionRuntimeType schema = context.getActionDefinition(action);
256                 return new ActionCodecContext(
257                     new ContainerLikeCodecContext(asClass(args[inputOffset], RpcInput.class), schema.input(),
258                         BindingCodecContext.this),
259                     new ContainerLikeCodecContext(asClass(args[outputOffset], RpcOutput.class), schema.output(),
260                         BindingCodecContext.this));
261             }
262
263             private static <T extends DataObject> Class<? extends T> asClass(final Type type, final Class<T> target) {
264                 verify(type instanceof Class, "Type %s is not a class", type);
265                 return ((Class<?>) type).asSubclass(target);
266             }
267         });
268
269     private final LoadingCache<Class<?>, NotificationCodecContext<?>> notificationsByClass = CacheBuilder.newBuilder()
270         .build(new CacheLoader<>() {
271             @Override
272             public NotificationCodecContext<?> load(final Class<?> key) {
273                 final var runtimeType = context.getTypes().bindingChild(JavaTypeName.create(key));
274                 if (runtimeType instanceof NotificationRuntimeType notification) {
275                     return new NotificationCodecContext<>(key, notification, BindingCodecContext.this);
276                 } if (runtimeType != null) {
277                     throw new IllegalArgumentException(key + " maps to unexpected " + runtimeType);
278                 }
279                 throw new IllegalArgumentException(key + " is not a known class");
280             }
281         });
282     private final LoadingCache<Absolute, NotificationCodecContext<?>> notificationsByPath =
283         CacheBuilder.newBuilder().build(new CacheLoader<>() {
284             @Override
285             public NotificationCodecContext<?> load(final Absolute key) {
286                 final var cls = context.getClassForSchema(key);
287                 try {
288                     return getNotificationContext(cls.asSubclass(Notification.class));
289                 } catch (ClassCastException e) {
290                     throw new IllegalArgumentException("Path " + key + " does not represent a notification", e);
291                 }
292             }
293         });
294
295     private final LoadingCache<Class<?>, ContainerLikeCodecContext<?>> rpcDataByClass =
296         CacheBuilder.newBuilder().build(new CacheLoader<>() {
297             @Override
298             public ContainerLikeCodecContext<?> load(final Class<?> key) {
299                 final var runtimeType = context.getTypes().findSchema(JavaTypeName.create(key))
300                     .orElseThrow(() -> new IllegalArgumentException(key + " is not a known class"));
301                 if (RpcInput.class.isAssignableFrom(key) && runtimeType instanceof InputRuntimeType input) {
302                     // FIXME: accurate type
303                     return new ContainerLikeCodecContext(key, input, BindingCodecContext.this);
304                 } else if (RpcOutput.class.isAssignableFrom(key) && runtimeType instanceof OutputRuntimeType output) {
305                     // FIXME: accurate type
306                     return new ContainerLikeCodecContext(key, output, BindingCodecContext.this);
307                 } else {
308                     throw new IllegalArgumentException(key + " maps to unexpected " + runtimeType);
309                 }
310             }
311         });
312     private final LoadingCache<Absolute, RpcInputCodec<?>> rpcDataByPath =
313         CacheBuilder.newBuilder().build(new CacheLoader<>() {
314             @Override
315             public RpcInputCodec<?> load(final Absolute key) {
316                 final var rpcName = key.firstNodeIdentifier();
317
318                 final Class<? extends DataContainer> container = switch (key.lastNodeIdentifier().getLocalName()) {
319                     case "input" -> context.getRpcInput(rpcName);
320                     case "output" -> context.getRpcOutput(rpcName);
321                     default -> throw new IllegalArgumentException("Unhandled path " + key);
322                 };
323
324                 return getRpc(container);
325             }
326         });
327
328     private final @NonNull BindingClassLoader loader =
329         BindingClassLoader.create(BindingCodecContext.class, BYTECODE_DIRECTORY);
330     private final @NonNull InstanceIdentifierCodec instanceIdentifierCodec;
331     private final @NonNull IdentityCodec identityCodec;
332     private final @NonNull BindingRuntimeContext context;
333
334     public BindingCodecContext() {
335         this(ServiceLoader.load(BindingRuntimeContext.class).findFirst()
336             .orElseThrow(() -> new IllegalStateException("Failed to load BindingRuntimeContext")));
337     }
338
339     public BindingCodecContext(final BindingRuntimeContext context) {
340         this.context = requireNonNull(context, "Binding Runtime Context is required.");
341         identityCodec = new IdentityCodec(context);
342         instanceIdentifierCodec = new InstanceIdentifierCodec(this);
343     }
344
345     @Override
346     public BindingRuntimeContext getRuntimeContext() {
347         return context;
348     }
349
350     @Override
351     public BindingClassLoader getLoader() {
352         return loader;
353     }
354
355     @Override
356     public IdentityCodec getIdentityCodec() {
357         return identityCodec;
358     }
359
360     @Override
361     public BindingInstanceIdentifierCodec getInstanceIdentifierCodec() {
362         return instanceIdentifierCodec;
363     }
364
365     @Override
366     public <T extends YangData<T>> BindingYangDataCodecTreeNode<T> getYangDataCodec(final Class<T> yangDataClass) {
367         throw new UnsupportedOperationException("Not implemented yet");
368     }
369
370     @Override
371     public BindingYangDataCodecTreeNode<?> getYangDataCodec(final YangDataName yangDataName) {
372         throw new UnsupportedOperationException("Not implemented yet");
373     }
374
375     @Override
376     public DataContainerSerializer getEventStreamSerializer(final Class<?> type) {
377         return serializers.getUnchecked(type);
378     }
379
380     @Override
381     public DataContainerStreamer<?> getDataContainerStreamer(final Class<?> type) {
382         return streamers.getUnchecked(type);
383     }
384
385     @Override
386     public DataContainerSerializer getSerializer(final Class<? extends DataContainer> type) {
387         return serializers.getUnchecked(type);
388     }
389
390     @Override
391     public Entry<YangInstanceIdentifier, BindingStreamEventWriter> newWriterAndIdentifier(
392             final InstanceIdentifier<?> path, final NormalizedNodeStreamWriter domWriter) {
393         final var yangArgs = new ArrayList<PathArgument>();
394         final var codecContext = getCodecContextNode(path, yangArgs);
395         return Map.entry(YangInstanceIdentifier.of(yangArgs),
396             new BindingToNormalizedStreamWriter(codecContext, domWriter));
397     }
398
399     @Override
400     public BindingStreamEventWriter newWriter(final InstanceIdentifier<?> path,
401             final NormalizedNodeStreamWriter domWriter) {
402         return new BindingToNormalizedStreamWriter(getCodecContextNode(path, null), domWriter);
403     }
404
405     @Override
406     public BindingStreamEventWriter newRpcWriter(final Class<? extends DataContainer> rpcInputOrOutput,
407             final NormalizedNodeStreamWriter domWriter) {
408         return new BindingToNormalizedStreamWriter(getRpc(rpcInputOrOutput), domWriter);
409     }
410
411     @Override
412     public BindingStreamEventWriter newNotificationWriter(final Class<? extends Notification<?>> notification,
413             final NormalizedNodeStreamWriter domWriter) {
414         return new BindingToNormalizedStreamWriter(getNotificationContext(notification), domWriter);
415     }
416
417     @Override
418     public BindingStreamEventWriter newActionInputWriter(final Class<? extends Action<?, ?, ?>> action,
419             final NormalizedNodeStreamWriter domWriter) {
420         return new BindingToNormalizedStreamWriter(getActionCodec(action).input(), domWriter);
421     }
422
423     @Override
424     public BindingStreamEventWriter newActionOutputWriter(final Class<? extends Action<?, ?, ?>> action,
425             final NormalizedNodeStreamWriter domWriter) {
426         return new BindingToNormalizedStreamWriter(getActionCodec(action).output(), domWriter);
427     }
428
429     @NonNull DataContainerCodecContext<?, ?, ?> getCodecContextNode(final InstanceIdentifier<?> binding,
430             final List<PathArgument> builder) {
431         final var it = binding.getPathArguments().iterator();
432         final var arg = it.next();
433
434         DataContainerCodecContext<?, ?, ?> current;
435         final var caseType = arg.getCaseType();
436         if (caseType.isPresent()) {
437             final @NonNull Class<? extends DataObject> type = caseType.orElseThrow();
438             final var choice = choicesByClass.getUnchecked(type);
439             choice.addYangPathArgument(arg, builder);
440             final var caze = choice.getStreamChild(type);
441             caze.addYangPathArgument(arg, builder);
442             current = caze.bindingPathArgumentChild(arg, builder);
443         } else {
444             final var child = getStreamChild(arg.getType());
445             child.addYangPathArgument(arg, builder);
446             current = child;
447         }
448
449         while (it.hasNext()) {
450             current = current.bindingPathArgumentChild(it.next(), builder);
451         }
452         return current;
453     }
454
455     /**
456      * Multi-purpose utility function. Traverse the codec tree, looking for
457      * the appropriate codec for the specified {@link YangInstanceIdentifier}.
458      * As a side-effect, gather all traversed binding {@link InstanceIdentifier.PathArgument}s
459      * into the supplied collection.
460      *
461      * @param dom {@link YangInstanceIdentifier} which is to be translated
462      * @param bindingArguments Collection for traversed path arguments
463      * @return Codec for target node, or {@code null} if the node does not have a binding representation (choice, case,
464      *         leaf).
465      * @throws IllegalArgumentException if {@code dom} is empty
466      */
467     @Nullable BindingDataObjectCodecTreeNode<?> getCodecContextNode(final @NonNull YangInstanceIdentifier dom,
468             final @Nullable Collection<InstanceIdentifier.PathArgument> bindingArguments) {
469         final var it = dom.getPathArguments().iterator();
470         if (!it.hasNext()) {
471             throw new IllegalArgumentException("Path may not be empty");
472         }
473
474         // First item is somewhat special:
475         // 1. it has to be a NodeIdentifier, otherwise it is a malformed identifier and we do not find it
476         var domArg = it.next();
477         if (!(domArg instanceof NodeIdentifier)) {
478             return null;
479         }
480         CodecContext nextNode = getOrRethrow(childrenByDomArg, domArg.getNodeType());
481
482         CodecContext currentNode;
483         if (nextNode instanceof ListCodecContext<?> listNode) {
484             // 2. if it is a list, we need to see if we are consuming another item.
485             if (!it.hasNext()) {
486                 // 2a: not further items: it boils down to a wildcard
487                 if (bindingArguments != null) {
488                     bindingArguments.add(listNode.getBindingPathArgument(null));
489                 }
490                 return listNode;
491             }
492
493             // 2b: there is a next item: it should either be a NodeIdentifier or a NodeIdentifierWithPredicates, but it
494             //     has to have the same node type
495             final var nextArg = it.next();
496             if (nextArg instanceof NodeWithValue || !nextArg.getNodeType().equals(domArg.getNodeType())) {
497                 throw new IllegalArgumentException(
498                     "List should be referenced two times in YANG Instance Identifier " + dom);
499             }
500             if (bindingArguments != null) {
501                 bindingArguments.add(listNode.getBindingPathArgument(nextArg));
502             }
503             currentNode = nextNode;
504         } else if (nextNode instanceof ChoiceCodecContext) {
505             currentNode = nextNode;
506         } else if (nextNode instanceof CommonDataObjectCodecContext<?, ?> firstContainer) {
507             if (bindingArguments != null) {
508                 bindingArguments.add(firstContainer.getBindingPathArgument(domArg));
509             }
510             currentNode = nextNode;
511         } else {
512             return null;
513         }
514
515         ListCodecContext<?> currentList = null;
516         while (it.hasNext()) {
517             domArg = it.next();
518             if (!(currentNode instanceof DataContainerCodecContext previous)) {
519                 throw new IllegalArgumentException("Unexpected child of non-container node " + currentNode);
520             }
521
522             nextNode = previous.yangPathArgumentChild(domArg);
523
524             /**
525              * Compatibility case: if it's determined the node belongs to augmentation
526              * then insert augmentation path argument in between.
527              */
528             if (nextNode instanceof AugmentationCodecContext<?> augmContext) {
529                 if (bindingArguments != null) {
530                     bindingArguments.add(augmContext.bindingArg());
531                 }
532                 currentNode = nextNode;
533                 nextNode = augmContext.yangPathArgumentChild(domArg);
534             }
535
536             /*
537              * List representation in YANG Instance Identifier consists of two arguments: first is list as a whole,
538              * second is list as an item so if it is /list it means list as whole, if it is /list/list - it is
539              * wildcarded and if it is /list/list[key] it is concrete item, all this variations are expressed in
540              * InstanceIdentifier as Item or IdentifiableItem
541              */
542             if (currentList != null) {
543                 checkArgument(currentList == nextNode,
544                         "List should be referenced two times in YANG Instance Identifier %s", dom);
545
546                 // We entered list, so now we have all information to emit
547                 // list path using second list argument.
548                 if (bindingArguments != null) {
549                     bindingArguments.add(currentList.getBindingPathArgument(domArg));
550                 }
551                 currentList = null;
552                 currentNode = nextNode;
553             } else if (nextNode instanceof ListCodecContext<?> listNode) {
554                 // We enter list, we do not update current Node yet,
555                 // since we need to verify
556                 currentList = listNode;
557             } else if (nextNode instanceof ChoiceCodecContext) {
558                 // We do not add path argument for choice, since
559                 // it is not supported by binding instance identifier.
560                 currentNode = nextNode;
561             } else if (nextNode instanceof CommonDataObjectCodecContext<?, ?> containerNode) {
562                 if (bindingArguments != null) {
563                     bindingArguments.add(containerNode.getBindingPathArgument(domArg));
564                 }
565                 currentNode = nextNode;
566             } else if (nextNode instanceof ValueNodeCodecContext) {
567                 LOG.debug("Instance identifier referencing a leaf is not representable ({})", dom);
568                 return null;
569             }
570         }
571
572         // Algorithm ended in list as whole representation
573         // we sill need to emit identifier for list
574         if (currentNode instanceof ChoiceCodecContext) {
575             LOG.debug("Instance identifier targeting a choice is not representable ({})", dom);
576             return null;
577         }
578         if (currentNode instanceof CaseCodecContext) {
579             LOG.debug("Instance identifier targeting a case is not representable ({})", dom);
580             return null;
581         }
582
583         if (currentList != null) {
584             if (bindingArguments != null) {
585                 bindingArguments.add(currentList.getBindingPathArgument(null));
586             }
587             return currentList;
588         }
589         if (currentNode != null) {
590             verify(currentNode instanceof BindingDataObjectCodecTreeNode, "Illegal return node %s for identifier %s",
591                 currentNode, dom);
592             return (BindingDataObjectCodecTreeNode<?>) currentNode;
593         }
594         return null;
595     }
596
597     NotificationCodecContext<?> getNotificationContext(final Absolute notification) {
598         return getOrRethrow(notificationsByPath, notification);
599     }
600
601     private NotificationCodecContext<?> getNotificationContext(final Class<?> notification) {
602         return getOrRethrow(notificationsByClass, notification);
603     }
604
605     ContainerLikeCodecContext<?> getRpc(final Class<? extends DataContainer> rpcInputOrOutput) {
606         return getOrRethrow(rpcDataByClass, rpcInputOrOutput);
607     }
608
609     RpcInputCodec<?> getRpcInputCodec(final Absolute containerPath) {
610         return getOrRethrow(rpcDataByPath, containerPath);
611     }
612
613     ActionCodecContext getActionCodec(final Class<? extends Action<?, ?, ?>> action) {
614         return getOrRethrow(actionsByClass, action);
615     }
616
617     @Override
618     public ImmutableMap<Method, ValueNodeCodecContext> getLeafNodes(final Class<?> type,
619             final EffectiveStatement<?, ?> schema) {
620         final var getterToLeafSchema = new HashMap<String, DataSchemaNode>();
621         for (var stmt : schema.effectiveSubstatements()) {
622             if (stmt instanceof TypedDataSchemaNode typedSchema) {
623                 putLeaf(getterToLeafSchema, typedSchema);
624             } else if (stmt instanceof AnydataSchemaNode anydataSchema) {
625                 putLeaf(getterToLeafSchema, anydataSchema);
626             } else if (stmt instanceof AnyxmlSchemaNode anyxmlSchema) {
627                 putLeaf(getterToLeafSchema, anyxmlSchema);
628             }
629         }
630         return getLeafNodesUsingReflection(type, getterToLeafSchema);
631     }
632
633     private static void putLeaf(final Map<String, DataSchemaNode> map, final DataSchemaNode leaf) {
634         map.put(BindingSchemaMapping.getGetterMethodName(leaf), leaf);
635     }
636
637     private ImmutableMap<Method, ValueNodeCodecContext> getLeafNodesUsingReflection(
638             final Class<?> parentClass, final Map<String, DataSchemaNode> getterToLeafSchema) {
639         final var leaves = new HashMap<Method, ValueNodeCodecContext>();
640         for (var method : parentClass.getMethods()) {
641             // Only consider non-bridge methods with no arguments
642             if (method.getParameterCount() == 0 && !method.isBridge()) {
643                 final DataSchemaNode schema = getterToLeafSchema.get(method.getName());
644
645                 final ValueNodeCodecContext valueNode;
646                 if (schema instanceof LeafSchemaNode leafSchema) {
647                     // FIXME: MDSAL-670: this is not right as we need to find a concrete type, but this may return
648                     //                   Object.class
649                     final Class<?> valueType = method.getReturnType();
650                     final ValueCodec<Object, Object> codec = getCodec(valueType, leafSchema.getType());
651                     valueNode = LeafNodeCodecContext.of(leafSchema, codec, method.getName(), valueType,
652                         context.modelContext());
653                 } else if (schema instanceof LeafListSchemaNode leafListSchema) {
654                     final Optional<Type> optType = ClassLoaderUtils.getFirstGenericParameter(
655                         method.getGenericReturnType());
656                     checkState(optType.isPresent(), "Failed to find return type for %s", method);
657
658                     final Class<?> valueType;
659                     final Type genericType = optType.orElseThrow();
660                     if (genericType instanceof Class<?> clazz) {
661                         valueType = clazz;
662                     } else if (genericType instanceof ParameterizedType parameterized) {
663                         valueType = (Class<?>) parameterized.getRawType();
664                     } else if (genericType instanceof WildcardType) {
665                         // FIXME: MDSAL-670: this is not right as we need to find a concrete type
666                         valueType = Object.class;
667                     } else {
668                         throw new IllegalStateException("Unexpected return type " + genericType);
669                     }
670
671                     final ValueCodec<Object, Object> codec = getCodec(valueType, leafListSchema.getType());
672                     valueNode = new LeafSetNodeCodecContext(leafListSchema, codec, method.getName());
673                 } else if (schema instanceof AnyxmlSchemaNode anyxmlSchema) {
674                     valueNode = new AnyxmlCodecContext<>(anyxmlSchema, method.getName(), opaqueReturnType(method),
675                         loader);
676                 } else if (schema instanceof AnydataSchemaNode anydataSchema) {
677                     valueNode = new AnydataCodecContext<>(anydataSchema, method.getName(), opaqueReturnType(method),
678                         loader);
679                 } else {
680                     verify(schema == null, "Unhandled schema %s for method %s", schema, method);
681                     // We do not have schema for leaf, so we will ignore it (e.g. getClass).
682                     continue;
683                 }
684
685                 leaves.put(method, valueNode);
686             }
687         }
688         return ImmutableMap.copyOf(leaves);
689     }
690
691     // FIXME: this is probably not right w.r.t. nulls
692     ValueCodec<Object, Object> getCodec(final Class<?> valueType, final TypeDefinition<?> instantiatedType) {
693         if (BaseIdentity.class.isAssignableFrom(valueType)) {
694             @SuppressWarnings({ "unchecked", "rawtypes" })
695             final ValueCodec<Object, Object> casted = (ValueCodec) identityCodec;
696             return casted;
697         } else if (InstanceIdentifier.class.equals(valueType)) {
698             @SuppressWarnings({ "unchecked", "rawtypes" })
699             final ValueCodec<Object, Object> casted = (ValueCodec) instanceIdentifierCodec;
700             return casted;
701         } else if (BindingReflections.isBindingClass(valueType)) {
702             return getCodecForBindingClass(valueType, instantiatedType);
703         }
704         // FIXME: MDSAL-670: this is right for most situations, but we must never return NOOP_CODEC for
705         //                   valueType=Object.class
706         return SchemaUnawareCodec.NOOP_CODEC;
707     }
708
709     @SuppressWarnings("checkstyle:illegalCatch")
710     // FIXME: this is probably not right w.r.t. nulls
711     private ValueCodec<Object, Object> getCodecForBindingClass(final Class<?> valueType,
712             final TypeDefinition<?> typeDef) {
713         if (typeDef instanceof IdentityrefTypeDefinition) {
714             return new CompositeValueCodec.OfIdentity(valueType, identityCodec);
715         } else if (typeDef instanceof InstanceIdentifierTypeDefinition) {
716             return new CompositeValueCodec.OfInstanceIdentifier(valueType, instanceIdentifierCodec);
717         } else if (typeDef instanceof UnionTypeDefinition unionType) {
718             try {
719                 return UnionTypeCodec.of(valueType, unionType, this);
720             } catch (Exception e) {
721                 throw new IllegalStateException("Unable to load codec for " + valueType, e);
722             }
723         } else if (typeDef instanceof LeafrefTypeDefinition) {
724             final var typeWithSchema = context.getTypeWithSchema(valueType);
725             final var schema = typeWithSchema.statement();
726             final TypeDefinition<?> def;
727             if (schema instanceof TypeDefinitionAware typeDefAware) {
728                 def = typeDefAware.getTypeDefinition();
729             } else if (schema instanceof TypeAware typeAware) {
730                 def = typeAware.getType();
731             } else {
732                 throw new IllegalStateException("Unexpected schema " + schema);
733             }
734             return getCodec(valueType, def);
735         }
736         return SchemaUnawareCodec.of(valueType, typeDef);
737     }
738
739     @Override
740     public IdentifiableItemCodec getPathArgumentCodec(final Class<?> listClz, final ListRuntimeType type) {
741         final Optional<Class<Key<?>>> optIdentifier = ClassLoaderUtils.findFirstGenericArgument(listClz,
742                 KeyAware.class);
743         checkState(optIdentifier.isPresent(), "Failed to find identifier for %s", listClz);
744
745         final Class<Key<?>> identifier = optIdentifier.orElseThrow();
746         final Map<QName, ValueContext> valueCtx = new HashMap<>();
747         for (final ValueNodeCodecContext leaf : getLeafNodes(identifier, type.statement()).values()) {
748             final QName name = leaf.getDomPathArgument().getNodeType();
749             valueCtx.put(name, new ValueContext(identifier, leaf));
750         }
751         return IdentifiableItemCodec.of(type.statement(), identifier, listClz, valueCtx);
752     }
753
754     @Override
755     @SuppressWarnings("unchecked")
756     public <E extends DataObject> DataContainerCodecContext<E, ?, ?> getStreamChild(final Class<E> childClass) {
757         final var result = Notification.class.isAssignableFrom(childClass) ? getNotificationContext(childClass)
758             : getOrRethrow(childrenByClass, childClass);
759         return (DataContainerCodecContext<E, ?, ?>) result;
760     }
761
762     @Override
763     @SuppressWarnings("unchecked")
764     public <A extends Augmentation<?>> BindingAugmentationCodecTreeNode<A> getAugmentationCodec(
765             final InstanceIdentifier<A> path) {
766         final var codecContext = getCodecContextNode(path, null);
767         if (codecContext instanceof BindingAugmentationCodecTreeNode) {
768             return (BindingAugmentationCodecTreeNode<A>) codecContext;
769         }
770         throw new IllegalArgumentException(path + " does not refer to an Augmentation");
771     }
772
773     @Override
774     @SuppressWarnings("unchecked")
775     public <T extends DataObject> BindingDataObjectCodecTreeNode<T> getDataObjectCodec(
776             final InstanceIdentifier<T> path) {
777         final var codecContext = getCodecContextNode(path, null);
778         if (codecContext instanceof BindingDataObjectCodecTreeNode) {
779             return (BindingDataObjectCodecTreeNode<T>) codecContext;
780         }
781         throw new IllegalArgumentException(path + " does not refer to a plain DataObject");
782     }
783
784     @Override
785     @SuppressWarnings("unchecked")
786     public <T extends DataObject> CodecWithPath<T> getSubtreeCodecWithPath(final InstanceIdentifier<T> path) {
787         final var yangArgs = new ArrayList<PathArgument>();
788         final var codecContext = getCodecContextNode(path, yangArgs);
789
790         // TODO Do we need defensive check here?
791         return new CodecWithPath<>((CommonDataObjectCodecTreeNode<T>) codecContext,
792             YangInstanceIdentifier.of(yangArgs));
793     }
794
795     @Override
796     @SuppressWarnings("unchecked")
797     public <T extends DataObject> CommonDataObjectCodecTreeNode<T> getSubtreeCodec(final InstanceIdentifier<T> path) {
798         // TODO Do we need defensive check here?
799         return (CommonDataObjectCodecTreeNode<T>) getCodecContextNode(path, null);
800     }
801
802     @Override
803     public BindingCodecTreeNode getSubtreeCodec(final YangInstanceIdentifier path) {
804         return getCodecContextNode(requireNonNull(path), null);
805     }
806
807     @Override
808     public BindingCodecTreeNode getSubtreeCodec(final Absolute path) {
809         throw new UnsupportedOperationException("Not implemented yet.");
810     }
811
812     @Override
813     public YangInstanceIdentifier toYangInstanceIdentifier(final InstanceIdentifier<?> binding) {
814         return instanceIdentifierCodec.fromBinding(binding);
815     }
816
817     @Override
818     public <T extends DataObject> InstanceIdentifier<T> fromYangInstanceIdentifier(final YangInstanceIdentifier dom) {
819         return instanceIdentifierCodec.toBinding(dom);
820     }
821
822     @Override
823     public <A extends Augmentation<?>> AugmentationResult toNormalizedAugmentation(final InstanceIdentifier<A> path,
824             final A data) {
825         final var result = toNormalizedNode(path, data);
826         if (result instanceof AugmentationResult augment) {
827             return augment;
828         }
829         throw new IllegalArgumentException(path + " does not identify an Augmentation");
830     }
831
832     @Override
833     public <T extends DataObject> NodeResult toNormalizedDataObject(final InstanceIdentifier<T> path, final T data) {
834         final var result = toNormalizedNode(path, data);
835         if (result instanceof NodeResult node) {
836             return node;
837         }
838         throw new IllegalArgumentException(path + " does not identify a plain DataObject");
839     }
840
841     @Override
842     public <T extends DataObject> NormalizedResult toNormalizedNode(final InstanceIdentifier<T> path, final T data) {
843         // We create Binding Stream Writer which translates from Binding to Normalized Nodes
844         final var yangArgs = new ArrayList<PathArgument>();
845         final var codecContext = getCodecContextNode(path, yangArgs);
846         final var yangPath = YangInstanceIdentifier.of(yangArgs);
847
848         // We create DOM stream writer which produces normalized nodes
849         final var result = new NormalizationResultHolder();
850         final var domWriter = ImmutableNormalizedNodeStreamWriter.from(result);
851         final var bindingWriter = new BindingToNormalizedStreamWriter(codecContext, domWriter);
852         final var augment = codecContext instanceof BindingAugmentationCodecTreeNode<?> augmentNode ? augmentNode
853             : null;
854
855         try {
856             // Augmentations do not have a representation, so we are faking a ContainerNode as the parent and we will be
857             // extracting the resulting children.
858             if (augment != null) {
859                 domWriter.startContainerNode(FAKE_NODEID, NormalizedNodeStreamWriter.UNKNOWN_SIZE);
860             }
861
862             // We get serializer which reads binding data and uses Binding To Normalized Node writer to write result
863             getSerializer(path.getTargetType()).serialize(data, bindingWriter);
864
865             if (augment != null) {
866                 domWriter.endNode();
867             }
868         } catch (final IOException e) {
869             LOG.error("Unexpected failure while serializing path {} data {}", path, data, e);
870             throw new IllegalStateException("Failed to create normalized node", e);
871         }
872
873         // Terminate the fake container and extract it to the result
874         if (augment != null) {
875             return new AugmentationResult(yangPath, augment.childPathArguments(),
876                 ImmutableList.copyOf(((ContainerNode) result.getResult().data()).body()));
877         }
878         return new NodeResult(yangPath, result.getResult().data());
879     }
880
881     @Override
882     public Entry<InstanceIdentifier<?>, DataObject> fromNormalizedNode(final YangInstanceIdentifier path,
883             final NormalizedNode data) {
884         if (notBindingRepresentable(data)) {
885             return null;
886         }
887
888         final var builder = new ArrayList<InstanceIdentifier.PathArgument>();
889         final var codec = getCodecContextNode(path, builder);
890         if (codec == null) {
891             if (data != null) {
892                 LOG.warn("Path {} does not have a binding equivalent, should have been caught earlier ({})", path,
893                     data.getClass());
894             }
895             return null;
896         }
897
898         final DataObject lazyObj = codec.deserialize(data);
899         final InstanceIdentifier<?> bindingPath = InstanceIdentifier.unsafeOf(builder);
900         return Map.entry(bindingPath, lazyObj);
901     }
902
903     @Override
904     public BaseNotification fromNormalizedNodeNotification(final Absolute path, final ContainerNode data) {
905         return getNotificationContext(path).deserialize(data);
906     }
907
908     @Override
909     public BaseNotification fromNormalizedNodeNotification(final Absolute path, final ContainerNode data,
910             final Instant eventInstant) {
911         return eventInstant == null ? fromNormalizedNodeNotification(path, data)
912                 : getNotificationContext(path).deserialize(data, eventInstant);
913     }
914
915     @Override
916     public DataObject fromNormalizedNodeRpcData(final Absolute containerPath, final ContainerNode data) {
917         return getRpcInputCodec(containerPath).deserialize(data);
918     }
919
920     @Override
921     public <T extends RpcInput> T fromNormalizedNodeActionInput(final Class<? extends Action<?, ?, ?>> action,
922             final ContainerNode input) {
923         return (T) requireNonNull(getActionCodec(action).input().deserialize(requireNonNull(input)));
924     }
925
926     @Override
927     public <T extends RpcOutput> T fromNormalizedNodeActionOutput(final Class<? extends Action<?, ?, ?>> action,
928             final ContainerNode output) {
929         return (T) requireNonNull(getActionCodec(action).output().deserialize(requireNonNull(output)));
930     }
931
932     @Override
933     @SuppressFBWarnings("BC_UNCONFIRMED_CAST")
934     public ContainerNode toNormalizedNodeNotification(final Notification<?> data) {
935         // FIXME: Should the cast to DataObject be necessary?
936         return serializeDataObject((DataObject) data,
937             (ctx, iface, domWriter) -> ctx.newNotificationWriter(
938                 (Class<? extends Notification<?>>) iface.asSubclass(Notification.class), domWriter));
939     }
940
941     @Override
942     public ContainerNode toNormalizedNodeNotification(final Absolute path, final BaseNotification data) {
943         checkArgument(data instanceof DataObject, "Unexpected data %s", data);
944         @SuppressWarnings("rawtypes")
945         final NotificationCodecContext notifContext = getNotificationContext(path);
946         @SuppressWarnings("unchecked")
947         final var result = notifContext.serialize((DataObject) data);
948         verify(result instanceof ContainerNode, "Unexpected result %s from %s", result, data);
949         return (ContainerNode) result;
950     }
951
952     @Override
953     @SuppressFBWarnings("BC_UNCONFIRMED_CAST")
954     public ContainerNode toNormalizedNodeRpcData(final DataContainer data) {
955         // FIXME: Should the cast to DataObject be necessary?
956         return serializeDataObject((DataObject) data, BindingNormalizedNodeWriterFactory::newRpcWriter);
957     }
958
959     @Override
960     public ContainerNode toNormalizedNodeActionInput(final Class<? extends Action<?, ?, ?>> action,
961             final RpcInput input) {
962         return serializeDataObject(input,(ctx, iface, domWriter) -> ctx.newActionInputWriter(action, domWriter));
963     }
964
965     @Override
966     public ContainerNode toNormalizedNodeActionOutput(final Class<? extends Action<?, ?, ?>> action,
967             final RpcOutput output) {
968         return serializeDataObject(output, (ctx, iface, domWriter) -> ctx.newActionOutputWriter(action, domWriter));
969     }
970
971     @Override
972     protected NodeIdentifier actionInputName(final Class<? extends Action<?, ?, ?>> action) {
973         return verifyNotNull(getActionCodec(action).input().getDomPathArgument());
974     }
975
976     @Override
977     protected NodeIdentifier actionOutputName(final Class<? extends Action<?, ?, ?>> action) {
978         return verifyNotNull(getActionCodec(action).output().getDomPathArgument());
979     }
980
981     private <T extends DataContainer> @NonNull ContainerNode serializeDataObject(final DataObject data,
982             final WriterFactoryMethod<T> newWriter) {
983         final var result = new NormalizationResultHolder();
984         // We create DOM stream writer which produces normalized nodes
985         final var domWriter = ImmutableNormalizedNodeStreamWriter.from(result);
986         final Class<? extends DataObject> type = data.implementedInterface();
987         @SuppressWarnings("unchecked")
988         final BindingStreamEventWriter writer = newWriter.createWriter(this, (Class<T>) type, domWriter);
989         try {
990             getSerializer(type).serialize(data, writer);
991         } catch (final IOException e) {
992             LOG.error("Unexpected failure while serializing data {}", data, e);
993             throw new IllegalStateException("Failed to create normalized node", e);
994         }
995         return (ContainerNode) result.getResult().data();
996     }
997
998     private static boolean notBindingRepresentable(final NormalizedNode data) {
999         // ValueNode covers LeafNode and LeafSetEntryNode
1000         return data instanceof ValueNode
1001             || data instanceof MapNode || data instanceof UnkeyedListNode
1002             || data instanceof ChoiceNode
1003             || data instanceof LeafSetNode;
1004     }
1005
1006     private static <K,V> V getOrRethrow(final LoadingCache<K, V> cache, final K key) {
1007         try {
1008             return cache.getUnchecked(key);
1009         } catch (UncheckedExecutionException e) {
1010             final var cause = e.getCause();
1011             if (cause != null) {
1012                 Throwables.throwIfUnchecked(cause);
1013             }
1014             throw e;
1015         }
1016     }
1017
1018     @SuppressWarnings("rawtypes")
1019     private static Class<? extends OpaqueObject> opaqueReturnType(final Method method) {
1020         final Class<?> valueType = method.getReturnType();
1021         verify(OpaqueObject.class.isAssignableFrom(valueType), "Illegal value type %s", valueType);
1022         return valueType.asSubclass(OpaqueObject.class);
1023     }
1024
1025     @FunctionalInterface
1026     private interface WriterFactoryMethod<T extends DataContainer> {
1027         BindingStreamEventWriter createWriter(@NonNull BindingNormalizedNodeWriterFactory factory,
1028                 @NonNull Class<? extends T> bindingClass, @NonNull NormalizedNodeStreamWriter domWriter);
1029     }
1030 }