2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12 import static com.google.common.base.Verify.verifyNotNull;
14 import com.google.common.base.VerifyException;
15 import com.google.common.collect.ImmutableMap;
16 import java.lang.invoke.MethodHandle;
17 import java.lang.invoke.MethodHandles;
18 import java.lang.invoke.MethodType;
19 import java.lang.reflect.Method;
20 import java.util.HashMap;
21 import java.util.List;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
25 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
26 import org.opendaylight.mdsal.binding.runtime.api.AugmentRuntimeType;
27 import org.opendaylight.mdsal.binding.runtime.api.AugmentableRuntimeType;
28 import org.opendaylight.mdsal.binding.runtime.api.ChoiceRuntimeType;
29 import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType;
30 import org.opendaylight.yangtools.yang.binding.Augmentable;
31 import org.opendaylight.yangtools.yang.binding.DataContainer;
32 import org.opendaylight.yangtools.yang.binding.DataObject;
33 import org.opendaylight.yangtools.yang.binding.OpaqueObject;
34 import org.opendaylight.yangtools.yang.binding.contract.Naming;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
36 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
39 * Analysis of a {@link DataObject} specialization class. The primary point of this class is to separate out creation
40 * indices needed for {@link #proxyConstructor}. Since we want to perform as much indexing as possible in a single pass,
41 * we also end up indexing things that are not strictly required to arrive at that constructor.
43 final class CodecDataObjectAnalysis<R extends CompositeRuntimeType> {
44 private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class,
45 AbstractDataObjectCodecContext.class, DataContainerNode.class);
46 private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class,
47 AbstractDataObjectCodecContext.class, DataContainerNode.class);
49 final @NonNull ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byStreamClass;
50 final @NonNull ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClass;
51 final @NonNull ImmutableMap<NodeIdentifier, NodeContextSupplier> byYang;
52 final @NonNull ImmutableMap<String, ValueNodeCodecContext> leafNodes;
53 final @NonNull Class<? extends CodecDataObject<?>> generatedClass;
54 final @NonNull List<AugmentRuntimeType> possibleAugmentations;
55 final @NonNull MethodHandle proxyConstructor;
57 CodecDataObjectAnalysis(final DataContainerCodecPrototype<R> prototype, final CodecItemFactory itemFactory,
58 final Method keyMethod) {
59 // Preliminaries from prototype
60 @SuppressWarnings("unchecked")
61 final Class<DataObject> bindingClass = Class.class.cast(prototype.getBindingClass());
62 final var runtimeType = prototype.getType();
63 final var factory = prototype.getFactory();
64 final var leafContexts = factory.getLeafNodes(bindingClass, runtimeType.statement());
66 // Reflection-based on the passed class
67 final var clsToMethod = getChildrenClassToMethod(bindingClass);
69 // Indexing part: be very careful around what gets done when
70 final var byYangBuilder = new HashMap<NodeIdentifier, NodeContextSupplier>();
72 // Step 1: add leaf children
73 var leafBuilder = ImmutableMap.<String, ValueNodeCodecContext>builderWithExpectedSize(leafContexts.size());
74 for (var leaf : leafContexts.values()) {
75 leafBuilder.put(leaf.getSchema().getQName().getLocalName(), leaf);
76 byYangBuilder.put(leaf.getDomPathArgument(), leaf);
78 leafNodes = leafBuilder.build();
80 final var byBindingArgClassBuilder = new HashMap<Class<?>, DataContainerCodecPrototype<?>>();
81 final var byStreamClassBuilder = new HashMap<Class<?>, DataContainerCodecPrototype<?>>();
82 final var daoProperties = new HashMap<Class<?>, PropertyInfo>();
83 for (var childDataObj : clsToMethod.entrySet()) {
84 final var method = childDataObj.getValue();
85 verify(!method.isDefault(), "Unexpected default method %s in %s", method, bindingClass);
87 final var retClass = childDataObj.getKey();
88 if (OpaqueObject.class.isAssignableFrom(retClass)) {
89 // Filter OpaqueObjects, they are not containers
93 // Record getter method
94 daoProperties.put(retClass, new PropertyInfo.Getter(method));
96 final var childProto = getChildPrototype(runtimeType, factory, itemFactory, retClass);
97 byStreamClassBuilder.put(childProto.getBindingClass(), childProto);
98 byYangBuilder.put(childProto.getYangArg(), childProto);
100 // FIXME: It really feels like we should be specializing DataContainerCodecPrototype so as to ditch
101 // createInstance() and then we could do an instanceof check instead.
102 if (childProto.getType() instanceof ChoiceRuntimeType) {
103 final var choice = (ChoiceNodeCodecContext<?>) childProto.get();
104 for (var cazeChild : choice.getCaseChildrenClasses()) {
105 byBindingArgClassBuilder.put(cazeChild, childProto);
110 // Snapshot before below processing
111 byStreamClass = ImmutableMap.copyOf(byStreamClassBuilder);
113 // Slight footprint optimization: we do not want to copy byStreamClass, as that would force its entrySet view
114 // to be instantiated. Furthermore the two maps can easily end up being equal -- hence we can reuse
115 // byStreamClass for the purposes of both.
116 byBindingArgClassBuilder.putAll(byStreamClassBuilder);
117 byBindingArgClass = byStreamClassBuilder.equals(byBindingArgClassBuilder) ? byStreamClass
118 : ImmutableMap.copyOf(byBindingArgClassBuilder);
120 // Find all non-default nonnullFoo() methods and update the corresponding property info
121 for (var entry : getChildrenClassToNonnullMethod(bindingClass).entrySet()) {
122 final var method = entry.getValue();
123 if (!method.isDefault()) {
124 daoProperties.compute(entry.getKey(), (key, value) -> new PropertyInfo.GetterAndNonnull(
125 verifyNotNull(value, "No getter for %s", key).getterMethod(), method));
128 // At this point all indexing is done: byYangBuilder should not be referenced
129 byYang = ImmutableMap.copyOf(byYangBuilder);
131 // Final bits: generate the appropriate class, As a side effect we identify what Augmentations are possible
132 if (Augmentable.class.isAssignableFrom(bindingClass)) {
133 // Verify we have the appropriate backing runtimeType
134 if (!(runtimeType instanceof AugmentableRuntimeType augmentableRuntimeType)) {
135 throw new VerifyException(
136 "Unexpected type %s backing augmenable %s".formatted(runtimeType, bindingClass));
139 possibleAugmentations = augmentableRuntimeType.augments();
140 generatedClass = CodecDataObjectGenerator.generateAugmentable(prototype.getFactory().getLoader(),
141 bindingClass, leafContexts, daoProperties, keyMethod);
143 possibleAugmentations = List.of();
144 generatedClass = CodecDataObjectGenerator.generate(prototype.getFactory().getLoader(), bindingClass,
145 leafContexts, daoProperties, keyMethod);
148 // All done: acquire the constructor: it is supposed to be public
149 final MethodHandle ctor;
151 ctor = MethodHandles.publicLookup().findConstructor(generatedClass, CONSTRUCTOR_TYPE);
152 } catch (NoSuchMethodException | IllegalAccessException e) {
153 throw new LinkageError("Failed to find contructor for class " + generatedClass, e);
156 proxyConstructor = ctor.asType(DATAOBJECT_TYPE);
159 private static @NonNull DataContainerCodecPrototype<?> getChildPrototype(final CompositeRuntimeType type,
160 final CodecContextFactory factory, final CodecItemFactory itemFactory,
161 final Class<? extends DataContainer> childClass) {
162 final var child = type.bindingChild(JavaTypeName.create(childClass));
164 throw DataContainerCodecContext.childNullException(factory.getRuntimeContext(), childClass,
165 "Node %s does not have child named %s", type, childClass);
168 return DataContainerCodecPrototype.from(itemFactory.createItem(childClass, child.statement()),
169 (CompositeRuntimeType) child, factory);
173 // FIXME: MDSAL-780: these methods perform analytics using java.lang.reflect to acquire the basic shape of the
174 // class. This is not exactly AOT friendly, as most of the information should be provided by
175 // CompositeRuntimeType.
177 // As as first cut, CompositeRuntimeType should provide the mapping between YANG children and the
178 // corresponding GETTER_PREFIX/NONNULL_PREFIX method names, If we have that, the use in this
179 // class should be fine.
181 // The second cut is binding the actual Method invocations, which is fine here, as this class is
182 // all about having a run-time generated class. AOT would be providing an alternative, where the
183 // equivalent class would be generated at compile-time and hence would bind to the methods
184 // directly -- and AOT equivalent of this class would really be a compile-time generated registry
185 // to those classes' entry points.
188 * Scans supplied class and returns an iterable of all data children classes.
190 * @param type YANG Modeled Entity derived from DataContainer
191 * @return Iterable of all data children, which have YANG modeled entity
193 private static Map<Class<? extends DataContainer>, Method> getChildrenClassToMethod(final Class<?> type) {
194 return getChildClassToMethod(type, Naming.GETTER_PREFIX);
197 private static Map<Class<? extends DataContainer>, Method> getChildrenClassToNonnullMethod(final Class<?> type) {
198 return getChildClassToMethod(type, Naming.NONNULL_PREFIX);
201 private static Map<Class<? extends DataContainer>, Method> getChildClassToMethod(final Class<?> type,
202 final String prefix) {
203 checkArgument(type != null, "Target type must not be null");
204 checkArgument(DataContainer.class.isAssignableFrom(type), "Supplied type %s must be derived from DataContainer",
206 final var ret = new HashMap<Class<? extends DataContainer>, Method>();
207 for (Method method : type.getMethods()) {
208 DataContainerCodecContext.getYangModeledReturnType(method, prefix)
209 .ifPresent(entity -> ret.put(entity, method));