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.collect.ImmutableMap;
15 import java.lang.reflect.Method;
16 import java.lang.reflect.ParameterizedType;
17 import java.util.HashMap;
18 import java.util.List;
20 import java.util.Optional;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
23 import org.opendaylight.mdsal.binding.runtime.api.ChoiceRuntimeType;
24 import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType;
25 import org.opendaylight.mdsal.binding.runtime.api.ContainerLikeRuntimeType;
26 import org.opendaylight.mdsal.binding.runtime.api.ContainerRuntimeType;
27 import org.opendaylight.mdsal.binding.runtime.api.ListRuntimeType;
28 import org.opendaylight.yangtools.util.ClassLoaderUtils;
29 import org.opendaylight.yangtools.yang.binding.DataContainer;
30 import org.opendaylight.yangtools.yang.binding.OpaqueObject;
31 import org.opendaylight.yangtools.yang.binding.contract.Naming;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
33 import org.opendaylight.yangtools.yang.model.api.stmt.PresenceEffectiveStatement;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
38 * Analysis of a {@link DataContainer} specialization class. This includes things needed for
39 * {@link DataContainerCodecContext}'s methods as well as the appropriate run-time generated class.
41 abstract class AbstractDataContainerAnalysis<R extends CompositeRuntimeType> {
42 private static final Logger LOG = LoggerFactory.getLogger(AbstractDataContainerAnalysis.class);
44 // Needed for DataContainerCodecContext
45 final @NonNull ImmutableMap<Class<?>, CommonDataObjectCodecPrototype<?>> byStreamClass;
46 final @NonNull ImmutableMap<Class<?>, CommonDataObjectCodecPrototype<?>> byBindingArgClass;
47 final @NonNull ImmutableMap<NodeIdentifier, CodecContextSupplier> byYang;
48 final @NonNull ImmutableMap<String, ValueNodeCodecContext> leafNodes;
50 // Needed for generated classes
51 final @NonNull ImmutableMap<Method, ValueNodeCodecContext> leafContexts;
52 final @NonNull ImmutableMap<Class<?>, PropertyInfo> daoProperties;
54 AbstractDataContainerAnalysis(final Class<?> bindingClass, final R runtimeType, final CodecContextFactory factory,
55 final CodecItemFactory itemFactory) {
56 leafContexts = factory.getLeafNodes(bindingClass, runtimeType.statement());
58 // Reflection-based on the passed class
59 final var clsToMethod = getChildrenClassToMethod(bindingClass);
61 // Indexing part: be very careful around what gets done when
62 final var byYangBuilder = new HashMap<NodeIdentifier, CodecContextSupplier>();
64 // Step 1: add leaf children
65 var leafBuilder = ImmutableMap.<String, ValueNodeCodecContext>builderWithExpectedSize(leafContexts.size());
66 for (var leaf : leafContexts.values()) {
67 leafBuilder.put(leaf.getSchema().getQName().getLocalName(), leaf);
68 byYangBuilder.put(leaf.getDomPathArgument(), leaf);
70 leafNodes = leafBuilder.build();
72 final var byBindingArgClassBuilder = new HashMap<Class<?>, CommonDataObjectCodecPrototype<?>>();
73 final var byStreamClassBuilder = new HashMap<Class<?>, CommonDataObjectCodecPrototype<?>>();
74 final var daoPropertiesBuilder = new HashMap<Class<?>, PropertyInfo>();
75 for (var childDataObj : clsToMethod.entrySet()) {
76 final var method = childDataObj.getValue();
77 verify(!method.isDefault(), "Unexpected default method %s in %s", method, bindingClass);
79 final var retClass = childDataObj.getKey();
80 if (OpaqueObject.class.isAssignableFrom(retClass)) {
81 // Filter OpaqueObjects, they are not containers
85 // Record getter method
86 daoPropertiesBuilder.put(retClass, new PropertyInfo.Getter(method));
88 final var childProto = getChildPrototype(runtimeType, factory, itemFactory, retClass);
89 byStreamClassBuilder.put(childProto.getBindingClass(), childProto);
90 byYangBuilder.put(childProto.getYangArg(), childProto);
92 // FIXME: It really feels like we should be specializing DataContainerCodecPrototype so as to ditch
93 // createInstance() and then we could do an instanceof check instead.
94 if (childProto.getType() instanceof ChoiceRuntimeType) {
95 final var choice = (ChoiceCodecContext<?>) childProto.get();
96 for (var cazeChild : choice.getCaseChildrenClasses()) {
97 byBindingArgClassBuilder.put(cazeChild, childProto);
102 // Snapshot before below processing
103 byStreamClass = ImmutableMap.copyOf(byStreamClassBuilder);
105 // Slight footprint optimization: we do not want to copy byStreamClass, as that would force its entrySet view
106 // to be instantiated. Furthermore the two maps can easily end up being equal -- hence we can reuse
107 // byStreamClass for the purposes of both.
108 byBindingArgClassBuilder.putAll(byStreamClassBuilder);
109 byBindingArgClass = byStreamClassBuilder.equals(byBindingArgClassBuilder) ? byStreamClass
110 : ImmutableMap.copyOf(byBindingArgClassBuilder);
112 // Find all non-default nonnullFoo() methods and update the corresponding property info
113 for (var entry : getChildrenClassToNonnullMethod(bindingClass).entrySet()) {
114 final var method = entry.getValue();
115 if (!method.isDefault()) {
116 daoPropertiesBuilder.compute(entry.getKey(), (key, value) -> new PropertyInfo.GetterAndNonnull(
117 verifyNotNull(value, "No getter for %s", key).getterMethod(), method));
121 // At this point all indexing is done: byYangBuilder should not be referenced
122 byYang = ImmutableMap.copyOf(byYangBuilder);
123 daoProperties = ImmutableMap.copyOf(daoPropertiesBuilder);
126 private static @NonNull CommonDataObjectCodecPrototype<?> getChildPrototype(final CompositeRuntimeType type,
127 final CodecContextFactory factory, final CodecItemFactory itemFactory,
128 final Class<? extends DataContainer> childClass) {
129 final var child = type.bindingChild(JavaTypeName.create(childClass));
131 throw DataContainerCodecContext.childNullException(factory.getRuntimeContext(), childClass,
132 "Node %s does not have child named %s", type, childClass);
134 final var item = itemFactory.createItem(childClass, child.statement());
135 if (child instanceof ContainerLikeRuntimeType containerLike) {
136 if (child instanceof ContainerRuntimeType container
137 && container.statement().findFirstEffectiveSubstatement(PresenceEffectiveStatement.class).isEmpty()) {
138 return new StructuralContainerCodecPrototype(item, container, factory);
140 return new ContainerLikeCodecPrototype(item, containerLike, factory);
141 } else if (child instanceof ListRuntimeType list) {
142 return list.keyType() != null ? new MapCodecPrototype(item, list, factory)
143 : new ListCodecPrototype(item, list, factory);
144 } else if (child instanceof ChoiceRuntimeType choice) {
145 return new ChoiceCodecPrototype(item, choice, factory);
147 throw new UnsupportedOperationException("Unhandled type " + child);
152 // FIXME: MDSAL-780: these methods perform analytics using java.lang.reflect to acquire the basic shape of the
153 // class. This is not exactly AOT friendly, as most of the information should be provided by
154 // CompositeRuntimeType.
156 // As as first cut, CompositeRuntimeType should provide the mapping between YANG children and the
157 // corresponding GETTER_PREFIX/NONNULL_PREFIX method names, If we have that, the use in this
158 // class should be fine.
160 // The second cut is binding the actual Method invocations, which is fine here, as this class is
161 // all about having a run-time generated class. AOT would be providing an alternative, where the
162 // equivalent class would be generated at compile-time and hence would bind to the methods
163 // directly -- and AOT equivalent of this class would really be a compile-time generated registry
164 // to those classes' entry points.
167 * Scans supplied class and returns an iterable of all data children classes.
169 * @param type YANG Modeled Entity derived from DataContainer
170 * @return Iterable of all data children, which have YANG modeled entity
172 private static Map<Class<? extends DataContainer>, Method> getChildrenClassToMethod(final Class<?> type) {
173 return getChildClassToMethod(type, Naming.GETTER_PREFIX);
176 private static Map<Class<? extends DataContainer>, Method> getChildrenClassToNonnullMethod(final Class<?> type) {
177 return getChildClassToMethod(type, Naming.NONNULL_PREFIX);
180 private static Map<Class<? extends DataContainer>, Method> getChildClassToMethod(final Class<?> type,
181 final String prefix) {
182 checkArgument(type != null, "Target type must not be null");
183 checkArgument(DataContainer.class.isAssignableFrom(type), "Supplied type %s must be derived from DataContainer",
185 final var ret = new HashMap<Class<? extends DataContainer>, Method>();
186 for (Method method : type.getMethods()) {
187 getYangModeledReturnType(method, prefix).ifPresent(entity -> ret.put(entity, method));
192 static final Optional<Class<? extends DataContainer>> getYangModeledReturnType(final Method method,
193 final String prefix) {
194 final String methodName = method.getName();
195 if ("getClass".equals(methodName) || !methodName.startsWith(prefix) || method.getParameterCount() > 0) {
196 return Optional.empty();
199 final Class<?> returnType = method.getReturnType();
200 if (DataContainer.class.isAssignableFrom(returnType)) {
201 return optionalDataContainer(returnType);
202 } else if (List.class.isAssignableFrom(returnType)) {
203 return getYangModeledReturnType(method, 0);
204 } else if (Map.class.isAssignableFrom(returnType)) {
205 return getYangModeledReturnType(method, 1);
207 return Optional.empty();
210 @SuppressWarnings("checkstyle:illegalCatch")
211 private static Optional<Class<? extends DataContainer>> getYangModeledReturnType(final Method method,
212 final int parameterOffset) {
214 return ClassLoaderUtils.callWithClassLoader(method.getDeclaringClass().getClassLoader(),
215 () -> genericParameter(method.getGenericReturnType(), parameterOffset)
216 .flatMap(result -> result instanceof Class ? optionalCast((Class<?>) result) : Optional.empty()));
217 } catch (Exception e) {
219 * It is safe to log this this exception on debug, since this
220 * method should not fail. Only failures are possible if the
223 LOG.debug("Unable to find YANG modeled return type for {}", method, e);
225 return Optional.empty();
228 private static Optional<java.lang.reflect.Type> genericParameter(final java.lang.reflect.Type type,
230 if (type instanceof ParameterizedType parameterized) {
231 final var parameters = parameterized.getActualTypeArguments();
232 if (parameters.length > offset) {
233 return Optional.of(parameters[offset]);
236 return Optional.empty();
239 private static Optional<Class<? extends DataContainer>> optionalCast(final Class<?> type) {
240 return DataContainer.class.isAssignableFrom(type) ? optionalDataContainer(type) : Optional.empty();
243 private static Optional<Class<? extends DataContainer>> optionalDataContainer(final Class<?> type) {
244 return Optional.of(type.asSubclass(DataContainer.class));