Eliminate CodecItemFactory
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / BindingCodecContext.java
index 82ba6fac4d6649090eaa7a3127e9240254d0a5d1..c6cf57a0419836e57494780377694a82f1c890b0 100644 (file)
@@ -38,7 +38,6 @@ import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.ServiceLoader;
 import java.util.concurrent.ExecutionException;
-import java.util.function.BiFunction;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.kohsuke.MetaInfServices;
@@ -59,13 +58,14 @@ import org.opendaylight.mdsal.binding.loader.BindingClassLoader;
 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
 import org.opendaylight.mdsal.binding.runtime.api.ActionRuntimeType;
 import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeContext;
-import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeTypes;
 import org.opendaylight.mdsal.binding.runtime.api.ChoiceRuntimeType;
 import org.opendaylight.mdsal.binding.runtime.api.ContainerLikeRuntimeType;
 import org.opendaylight.mdsal.binding.runtime.api.ContainerRuntimeType;
 import org.opendaylight.mdsal.binding.runtime.api.DataRuntimeType;
+import org.opendaylight.mdsal.binding.runtime.api.InputRuntimeType;
 import org.opendaylight.mdsal.binding.runtime.api.ListRuntimeType;
 import org.opendaylight.mdsal.binding.runtime.api.NotificationRuntimeType;
+import org.opendaylight.mdsal.binding.runtime.api.OutputRuntimeType;
 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
 import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.util.ClassLoaderUtils;
@@ -76,6 +76,7 @@ import org.opendaylight.yangtools.yang.binding.BaseNotification;
 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
 import org.opendaylight.yangtools.yang.binding.DataContainer;
 import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.DataObjectStep;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.binding.Key;
 import org.opendaylight.yangtools.yang.binding.KeyAware;
@@ -85,9 +86,7 @@ import org.opendaylight.yangtools.yang.binding.OpaqueObject;
 import org.opendaylight.yangtools.yang.binding.RpcInput;
 import org.opendaylight.yangtools.yang.binding.RpcOutput;
 import org.opendaylight.yangtools.yang.binding.YangData;
-import org.opendaylight.yangtools.yang.binding.contract.Naming;
 import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.common.YangDataName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
@@ -106,13 +105,10 @@ import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolde
 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ContainerLike;
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.Module;
-import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.opendaylight.yangtools.yang.model.api.TypeAware;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
@@ -156,10 +152,10 @@ public final class BindingCodecContext extends AbstractBindingNormalizedNodeSeri
                 return new DataContainerSerializer(BindingCodecContext.this, streamers.get(key));
             }
         });
-    private final LoadingCache<Class<? extends DataObject>, DataContainerCodecContext<?, ?>> childrenByClass =
+    private final LoadingCache<Class<? extends DataObject>, DataContainerCodecContext<?, ?, ?>> childrenByClass =
         CacheBuilder.newBuilder().build(new CacheLoader<>() {
             @Override
-            public DataContainerCodecContext<?, ?> load(final Class<? extends DataObject> key) {
+            public DataContainerCodecContext<?, ?, ?> load(final Class<? extends DataObject> key) {
                 final var childSchema = context.getTypes().bindingChild(JavaTypeName.create(key));
                 if (childSchema instanceof ContainerLikeRuntimeType containerLike) {
                     if (childSchema instanceof ContainerRuntimeType container
@@ -172,7 +168,7 @@ public final class BindingCodecContext extends AbstractBindingNormalizedNodeSeri
                     return list.keyType() == null ? new ListCodecContext<>(key, list, BindingCodecContext.this)
                         : MapCodecContext.of(key, list, BindingCodecContext.this);
                 } else if (childSchema instanceof ChoiceRuntimeType choice) {
-                    return new ChoiceCodecContext<>(key, choice, BindingCodecContext.this);
+                    return new ChoiceCodecContext<>(key.asSubclass(ChoiceIn.class), choice, BindingCodecContext.this);
                 } else if (childSchema == null) {
                     throw DataContainerCodecContext.childNullException(context, key, "%s is not top-level item.", key);
                 } else {
@@ -182,15 +178,15 @@ public final class BindingCodecContext extends AbstractBindingNormalizedNodeSeri
         });
 
     // FIXME: this could also be a leaf!
-    private final LoadingCache<QName, DataContainerCodecContext<?, ?>> childrenByDomArg =
+    private final LoadingCache<QName, DataContainerCodecContext<?, ?, ?>> childrenByDomArg =
         CacheBuilder.newBuilder().build(new CacheLoader<>() {
             @Override
-            public DataContainerCodecContext<?, ?> load(final QName qname) throws ClassNotFoundException {
+            public DataContainerCodecContext<?, ?, ?> load(final QName qname) throws ClassNotFoundException {
                 final var type = context.getTypes();
                 final var child = type.schemaTreeChild(qname);
                 if (child == null) {
                     final var module = qname.getModule();
-                    if (context.getEffectiveModelContext().findModule(module).isEmpty()) {
+                    if (context.modelContext().findModule(module).isEmpty()) {
                         throw new MissingSchemaException(
                             "Module " + module + " is not present in current schema context.");
                     }
@@ -275,15 +271,13 @@ public final class BindingCodecContext extends AbstractBindingNormalizedNodeSeri
         .build(new CacheLoader<>() {
             @Override
             public NotificationCodecContext<?> load(final Class<?> key) {
-                // FIXME: sharpen check to an Notification.class
-                checkArgument(key.isInterface(), "Supplied class must be interface.");
-
-                // TODO: we should be able to work with bindingChild() instead of schemaTreeChild() here
-                final var qname = BindingReflections.findQName(key);
-                if (context.getTypes().schemaTreeChild(qname) instanceof NotificationRuntimeType type) {
-                    return new NotificationCodecContext<>(key, type, BindingCodecContext.this);
+                final var runtimeType = context.getTypes().bindingChild(JavaTypeName.create(key));
+                if (runtimeType instanceof NotificationRuntimeType notification) {
+                    return new NotificationCodecContext<>(key, notification, BindingCodecContext.this);
+                } if (runtimeType != null) {
+                    throw new IllegalArgumentException(key + " maps to unexpected " + runtimeType);
                 }
-                throw new IllegalArgumentException("Supplied " + key + " is not valid notification");
+                throw new IllegalArgumentException(key + " is not a known class");
             }
         });
     private final LoadingCache<Absolute, NotificationCodecContext<?>> notificationsByPath =
@@ -303,61 +297,17 @@ public final class BindingCodecContext extends AbstractBindingNormalizedNodeSeri
         CacheBuilder.newBuilder().build(new CacheLoader<>() {
             @Override
             public ContainerLikeCodecContext<?> load(final Class<?> key) {
-                final BiFunction<BindingRuntimeTypes, QName, Optional<? extends ContainerLikeRuntimeType<?, ?>>> lookup;
-                if (RpcInput.class.isAssignableFrom(key)) {
-                    lookup = BindingRuntimeTypes::findRpcInput;
-                } else if (RpcOutput.class.isAssignableFrom(key)) {
-                    lookup = BindingRuntimeTypes::findRpcOutput;
+                final var runtimeType = context.getTypes().findSchema(JavaTypeName.create(key))
+                    .orElseThrow(() -> new IllegalArgumentException(key + " is not a known class"));
+                if (RpcInput.class.isAssignableFrom(key) && runtimeType instanceof InputRuntimeType input) {
+                    // FIXME: accurate type
+                    return new ContainerLikeCodecContext(key, input, BindingCodecContext.this);
+                } else if (RpcOutput.class.isAssignableFrom(key) && runtimeType instanceof OutputRuntimeType output) {
+                    // FIXME: accurate type
+                    return new ContainerLikeCodecContext(key, output, BindingCodecContext.this);
                 } else {
-                    throw new IllegalArgumentException(key + " does not represent an RPC container");
+                    throw new IllegalArgumentException(key + " maps to unexpected " + runtimeType);
                 }
-
-                final QName qname = BindingReflections.findQName(key);
-                final QNameModule qnameModule = qname.getModule();
-                final Module module = context.getEffectiveModelContext().findModule(qnameModule)
-                    .orElseThrow(() -> new IllegalArgumentException("Failed to find module for " + qnameModule));
-                final String className = Naming.getClassName(qname);
-
-                for (var potential : module.getRpcs()) {
-                    final QName potentialQName = potential.getQName();
-                    /*
-                     * Check if rpc and class represents data from same module and then checks if rpc local name
-                     * produces same class name as class name appended with Input/Output based on QName associated
-                     * with binding class.
-                     *
-                     * FIXME: Rework this to have more precise logic regarding Binding Specification.
-                     */
-                    if (key.getSimpleName().equals(Naming.getClassName(potentialQName) + className)) {
-                        final ContainerLike schema = getRpcDataSchema(potential, qname);
-                        checkArgument(schema != null, "Schema for %s does not define input / output.", potentialQName);
-
-                        final var type = lookup.apply(context.getTypes(), potentialQName)
-                            .orElseThrow(() -> new IllegalArgumentException("Cannot find runtime type for " + key));
-
-                        // FIXME: accurate type
-                        return new ContainerLikeCodecContext(key, type, BindingCodecContext.this);
-                    }
-                }
-
-                throw new IllegalArgumentException("Supplied class " + key + " is not valid RPC class.");
-            }
-
-            /**
-             * Returns RPC input or output schema based on supplied QName.
-             *
-             * @param rpc RPC Definition
-             * @param qname input or output QName with namespace same as RPC
-             * @return input or output schema. Returns null if RPC does not have input/output specified.
-             */
-            private static @Nullable ContainerLike getRpcDataSchema(final @NonNull RpcDefinition rpc,
-                    final @NonNull QName qname) {
-                requireNonNull(rpc, "Rpc Schema must not be null");
-                return switch (requireNonNull(qname, "QName must not be null").getLocalName()) {
-                    case "input" -> rpc.getInput();
-                    case "output" -> rpc.getOutput();
-                    default -> throw new IllegalArgumentException(
-                        "Supplied qname " + qname + " does not represent rpc input or output.");
-                };
             }
         });
     private final LoadingCache<Absolute, RpcInputCodec<?>> rpcDataByPath =
@@ -477,26 +427,26 @@ public final class BindingCodecContext extends AbstractBindingNormalizedNodeSeri
         return new BindingToNormalizedStreamWriter(getActionCodec(action).output(), domWriter);
     }
 
-    @NonNull DataContainerCodecContext<?,?> getCodecContextNode(final InstanceIdentifier<?> binding,
+    @NonNull DataContainerCodecContext<?, ?, ?> getCodecContextNode(final InstanceIdentifier<?> binding,
             final List<PathArgument> builder) {
         final var it = binding.getPathArguments().iterator();
-        final var arg = it.next();
-
-        DataContainerCodecContext<?, ?> current;
-        final var caseType = arg.getCaseType();
-        if (caseType.isPresent()) {
-            final @NonNull Class<? extends DataObject> type = caseType.orElseThrow();
-            final var choice = choicesByClass.getUnchecked(type);
-            choice.addYangPathArgument(arg, builder);
-            final var caze = choice.getStreamChild(type);
-            caze.addYangPathArgument(arg, builder);
-            current = caze.bindingPathArgumentChild(arg, builder);
+        final var step = it.next();
+
+        final DataContainerCodecContext<?, ?, ?> start;
+        final var caseType = step.caseType();
+        if (caseType != null) {
+            final var choice = choicesByClass.getUnchecked(caseType);
+            choice.addYangPathArgument(step, builder);
+            final var caze = choice.getStreamChild(caseType);
+            caze.addYangPathArgument(step, builder);
+            start = caze.bindingPathArgumentChild(step, builder);
         } else {
-            final var child = getStreamChild(arg.getType());
-            child.addYangPathArgument(arg, builder);
-            current = child;
+            final var child = getStreamChild(step.type());
+            child.addYangPathArgument(step, builder);
+            start = child;
         }
 
+        var current = start;
         while (it.hasNext()) {
             current = current.bindingPathArgumentChild(it.next(), builder);
         }
@@ -516,7 +466,7 @@ public final class BindingCodecContext extends AbstractBindingNormalizedNodeSeri
      * @throws IllegalArgumentException if {@code dom} is empty
      */
     @Nullable BindingDataObjectCodecTreeNode<?> getCodecContextNode(final @NonNull YangInstanceIdentifier dom,
-            final @Nullable Collection<InstanceIdentifier.PathArgument> bindingArguments) {
+            final @Nullable Collection<DataObjectStep<?>> bindingArguments) {
         final var it = dom.getPathArguments().iterator();
         if (!it.hasNext()) {
             throw new IllegalArgumentException("Path may not be empty");
@@ -700,7 +650,7 @@ public final class BindingCodecContext extends AbstractBindingNormalizedNodeSeri
                     final Class<?> valueType = method.getReturnType();
                     final ValueCodec<Object, Object> codec = getCodec(valueType, leafSchema.getType());
                     valueNode = LeafNodeCodecContext.of(leafSchema, codec, method.getName(), valueType,
-                        context.getEffectiveModelContext());
+                        context.modelContext());
                 } else if (schema instanceof LeafListSchemaNode leafListSchema) {
                     final Optional<Type> optType = ClassLoaderUtils.getFirstGenericParameter(
                         method.getGenericReturnType());
@@ -804,10 +754,10 @@ public final class BindingCodecContext extends AbstractBindingNormalizedNodeSeri
 
     @Override
     @SuppressWarnings("unchecked")
-    public <E extends DataObject> CommonDataObjectCodecContext<E, ?> getStreamChild(final Class<E> childClass) {
+    public <E extends DataObject> DataContainerCodecContext<E, ?, ?> getStreamChild(final Class<E> childClass) {
         final var result = Notification.class.isAssignableFrom(childClass) ? getNotificationContext(childClass)
             : getOrRethrow(childrenByClass, childClass);
-        return (CommonDataObjectCodecContext<E, ?>) result;
+        return (DataContainerCodecContext<E, ?, ?>) result;
     }
 
     @Override
@@ -936,7 +886,7 @@ public final class BindingCodecContext extends AbstractBindingNormalizedNodeSeri
             return null;
         }
 
-        final var builder = new ArrayList<InstanceIdentifier.PathArgument>();
+        final var builder = new ArrayList<DataObjectStep<?>>();
         final var codec = getCodecContextNode(path, builder);
         if (codec == null) {
             if (data != null) {