Retain protype in DataContainerCodecContext
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / DataContainerCodecContext.java
index 2caa3667acb2815b8d6c39a72523f317e162c355..a1fd1bfc29658831fe40508ca921dbf54ba0b701 100644 (file)
@@ -12,16 +12,15 @@ import static java.util.Objects.requireNonNull;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableSet;
 import edu.umd.cs.findbugs.annotations.CheckReturnValue;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.VarHandle;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
-import java.lang.reflect.ParameterizedType;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import org.eclipse.jdt.annotation.NonNull;
@@ -29,7 +28,6 @@ import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataContainerCodecTreeNode;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeCachingCodec;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeCodec;
-import org.opendaylight.mdsal.binding.dom.codec.api.BindingStreamEventWriter;
 import org.opendaylight.mdsal.binding.dom.codec.api.IncorrectNestingException;
 import org.opendaylight.mdsal.binding.dom.codec.api.MissingClassInLoadingStrategyException;
 import org.opendaylight.mdsal.binding.dom.codec.api.MissingSchemaException;
@@ -37,8 +35,6 @@ import org.opendaylight.mdsal.binding.dom.codec.api.MissingSchemaForClassExcepti
 import org.opendaylight.mdsal.binding.model.api.Type;
 import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeContext;
 import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType;
-import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
-import org.opendaylight.mdsal.binding.runtime.api.RuntimeTypeContainer;
 import org.opendaylight.yangtools.util.ClassLoaderUtils;
 import org.opendaylight.yangtools.yang.binding.Augmentable;
 import org.opendaylight.yangtools.yang.binding.Augmentation;
@@ -47,8 +43,9 @@ import org.opendaylight.yangtools.yang.binding.DataContainer;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
@@ -65,9 +62,10 @@ import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-abstract sealed class DataContainerCodecContext<D extends BindingObject & DataContainer, T extends RuntimeTypeContainer>
+abstract sealed class DataContainerCodecContext<D extends DataContainer, R extends CompositeRuntimeType,
+        P extends DataContainerPrototype<?, R>>
         extends CodecContext implements BindingDataContainerCodecTreeNode<D>
-        permits CommonDataObjectCodecContext, RootCodecContext {
+        permits CommonDataObjectCodecContext {
     private static final Logger LOG = LoggerFactory.getLogger(DataContainerCodecContext.class);
     private static final VarHandle EVENT_STREAM_SERIALIZER;
 
@@ -80,17 +78,21 @@ abstract sealed class DataContainerCodecContext<D extends BindingObject & DataCo
         }
     }
 
+    private final @NonNull P prototype;
     private final @NonNull ChildAddressabilitySummary childAddressabilitySummary;
 
     // Accessed via a VarHandle
     @SuppressWarnings("unused")
+    @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
     private volatile DataContainerSerializer eventStreamSerializer;
 
-    DataContainerCodecContext(final T type) {
-        childAddressabilitySummary = type instanceof RuntimeType runtimeType
-            ? computeChildAddressabilitySummary(runtimeType.statement())
-                // BindingRuntimeTypes, does not matter
-                : ChildAddressabilitySummary.MIXED;
+    DataContainerCodecContext(final P prototype) {
+        this.prototype = requireNonNull(prototype);
+        childAddressabilitySummary = computeChildAddressabilitySummary(prototype.runtimeType().statement());
+    }
+
+    final @NonNull P prototype() {
+        return prototype;
     }
 
     @Override
@@ -98,28 +100,22 @@ abstract sealed class DataContainerCodecContext<D extends BindingObject & DataCo
         return childAddressabilitySummary;
     }
 
-    protected abstract @NonNull CodecContextFactory factory();
-
-    protected abstract @NonNull T type();
-
-    /**
-     * Returns nested node context using supplied YANG Instance Identifier.
-     *
-     * @param arg Yang Instance Identifier Argument
-     * @return Context of child
-     * @throws IllegalArgumentException If supplied argument does not represent valid child.
-     */
+    // Non-final for ChoiceCodecContext
     @Override
-    public abstract CodecContext yangPathArgumentChild(YangInstanceIdentifier.PathArgument arg);
+    public CodecContext yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) {
+        CodecContextSupplier supplier;
+        if (arg instanceof NodeIdentifier nodeId) {
+            supplier = yangChildSupplier(nodeId);
+        } else if (arg instanceof NodeIdentifierWithPredicates nip) {
+            supplier = yangChildSupplier(new NodeIdentifier(nip.getNodeType()));
+        } else {
+            supplier = null;
+        }
+        return childNonNull(supplier, arg, "Argument %s is not valid child of %s", arg, getSchema()).getCodecContext();
+    }
+
+    abstract @Nullable CodecContextSupplier yangChildSupplier(@NonNull NodeIdentifier arg);
 
-    /**
-     * Returns nested node context using supplied Binding Instance Identifier
-     * and adds YANG instance identifiers to supplied list.
-     *
-     * @param arg Binding Instance Identifier Argument
-     * @return Context of child or null if supplied {@code arg} does not represent valid child.
-     * @throws IllegalArgumentException If supplied argument does not represent valid child.
-     */
     @Override
     public CommonDataObjectCodecContext<?, ?> bindingPathArgumentChild(final PathArgument arg,
             final List<YangInstanceIdentifier.PathArgument> builder) {
@@ -148,24 +144,26 @@ abstract sealed class DataContainerCodecContext<D extends BindingObject & DataCo
     }
 
     @Override
-    public abstract <C extends DataObject> CommonDataObjectCodecContext<C, ?> getStreamChild(Class<C> childClass);
+    public final <C extends DataObject> CommonDataObjectCodecContext<C, ?> getStreamChild(final Class<C> childClass) {
+        return childNonNull(streamChild(childClass), childClass,
+            "Child %s is not valid child of %s", getBindingClass(), childClass);
+    }
 
-    /**
-     * Returns child context as if it was walked by {@link BindingStreamEventWriter}. This means that to enter case, one
-     * must issue getChild(ChoiceClass).getChild(CaseClass).
-     *
-     * @param childClass child class
-     * @return Context of child or Optional.empty is supplied class is not applicable in context.
-     */
+    @SuppressWarnings("unchecked")
     @Override
-    public abstract <C extends DataObject> CommonDataObjectCodecContext<C, ?> streamChild(Class<C> childClass);
+    public final <C extends DataObject> CommonDataObjectCodecContext<C, ?> streamChild(final Class<C> childClass) {
+        final var childProto = streamChildPrototype(requireNonNull(childClass));
+        return childProto == null ? null : (CommonDataObjectCodecContext<C, ?>) childProto.getCodecContext();
+    }
+
+    abstract @Nullable CommonDataObjectCodecPrototype<?> streamChildPrototype(@NonNull Class<?> childClass);
 
     @Override
     public String toString() {
         return getClass().getSimpleName() + " [" + getBindingClass() + "]";
     }
 
-    static final <T extends DataObject, C extends DataContainerCodecContext<T, ?> & BindingNormalizedNodeCodec<T>>
+    static final <T extends DataObject, C extends DataContainerCodecContext<T, ?, ?> & BindingNormalizedNodeCodec<T>>
             @NonNull BindingNormalizedNodeCachingCodec<T> createCachingCodec(final C context,
                 final ImmutableCollection<Class<? extends BindingObject>> cacheSpecifier) {
         return cacheSpecifier.isEmpty() ? new NonCachingCodec<>(context)
@@ -198,8 +196,8 @@ abstract sealed class DataContainerCodecContext<D extends BindingObject & DataCo
 
     @CheckReturnValue
     private IllegalArgumentException childNullException(final QName child, final String message, final Object... args) {
-        final QNameModule module = child.getModule();
-        if (!factory().getRuntimeContext().getEffectiveModelContext().findModule(module).isPresent()) {
+        final var module = child.getModule();
+        if (!prototype().contextFactory().getRuntimeContext().modelContext().findModule(module).isPresent()) {
             return new MissingSchemaException("Module " + module + " is not present in current schema context.");
         }
         return new IncorrectNestingException(message, args);
@@ -208,7 +206,7 @@ abstract sealed class DataContainerCodecContext<D extends BindingObject & DataCo
     @CheckReturnValue
     private @NonNull IllegalArgumentException childNullException(final Class<?> childClass, final String message,
             final Object... args) {
-        return childNullException(factory().getRuntimeContext(), childClass, message, args);
+        return childNullException(prototype().contextFactory().getRuntimeContext(), childClass, message, args);
     }
 
     @CheckReturnValue
@@ -241,7 +239,7 @@ abstract sealed class DataContainerCodecContext<D extends BindingObject & DataCo
 
     // Split out to aid inlining
     private DataContainerSerializer loadEventStreamSerializer() {
-        final DataContainerSerializer loaded = factory().getEventStreamSerializer(getBindingClass());
+        final DataContainerSerializer loaded = prototype().contextFactory().getEventStreamSerializer(getBindingClass());
         final Object witness = EVENT_STREAM_SERIALIZER.compareAndExchangeRelease(this, null, loaded);
         return witness == null ? loaded : (DataContainerSerializer) witness;
     }
@@ -267,63 +265,6 @@ abstract sealed class DataContainerCodecContext<D extends BindingObject & DataCo
         }
     }
 
-    // FIXME: MDSAL-780 replace this method with BindingRuntimeTypes-driven logic
-    static final Optional<Class<? extends DataContainer>> getYangModeledReturnType(final Method method,
-            final String prefix) {
-        final String methodName = method.getName();
-        if ("getClass".equals(methodName) || !methodName.startsWith(prefix) || method.getParameterCount() > 0) {
-            return Optional.empty();
-        }
-
-        final Class<?> returnType = method.getReturnType();
-        if (DataContainer.class.isAssignableFrom(returnType)) {
-            return optionalDataContainer(returnType);
-        } else if (List.class.isAssignableFrom(returnType)) {
-            return getYangModeledReturnType(method, 0);
-        } else if (Map.class.isAssignableFrom(returnType)) {
-            return getYangModeledReturnType(method, 1);
-        }
-        return Optional.empty();
-    }
-
-    @SuppressWarnings("checkstyle:illegalCatch")
-    private static Optional<Class<? extends DataContainer>> getYangModeledReturnType(final Method method,
-            final int parameterOffset) {
-        try {
-            return ClassLoaderUtils.callWithClassLoader(method.getDeclaringClass().getClassLoader(),
-                () -> genericParameter(method.getGenericReturnType(), parameterOffset)
-                    .flatMap(result -> result instanceof Class ? optionalCast((Class<?>) result) : Optional.empty()));
-        } catch (Exception e) {
-            /*
-             * It is safe to log this this exception on debug, since this
-             * method should not fail. Only failures are possible if the
-             * runtime / backing.
-             */
-            LOG.debug("Unable to find YANG modeled return type for {}", method, e);
-        }
-        return Optional.empty();
-    }
-
-    private static Optional<java.lang.reflect.Type> genericParameter(final java.lang.reflect.Type type,
-            final int offset) {
-        if (type instanceof ParameterizedType parameterized) {
-            final var parameters = parameterized.getActualTypeArguments();
-            if (parameters.length > offset) {
-                return Optional.of(parameters[offset]);
-            }
-        }
-        return Optional.empty();
-    }
-
-    private static Optional<Class<? extends DataContainer>> optionalCast(final Class<?> type) {
-        return DataContainer.class.isAssignableFrom(type) ? optionalDataContainer(type) : Optional.empty();
-    }
-
-    // FIXME: MDSAL-780: remove this method
-    static final Optional<Class<? extends DataContainer>> optionalDataContainer(final Class<?> type) {
-        return Optional.of(type.asSubclass(DataContainer.class));
-    }
-
     /**
      * Determines if two augmentation classes or case classes represents same data.
      *
@@ -399,8 +340,6 @@ abstract sealed class DataContainerCodecContext<D extends BindingObject & DataCo
         return opt.orElse(null);
     }
 
-
-
     private static @NonNull ChildAddressabilitySummary computeChildAddressabilitySummary(final Object nodeSchema) {
         // FIXME: rework this to work on EffectiveStatements
         if (nodeSchema instanceof DataNodeContainer contaner) {