Split out NotificationCodecContext.Prototype
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / DataContainerCodecPrototype.java
index 35b37a1e76b6ca6067f3a03216fb4b7bed62b02f..4fcbf16d5df08d10a8e1cb632b1301937cd9d585 100644 (file)
  */
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
-import com.google.common.collect.Iterables;
-import org.checkerframework.checker.lock.qual.Holding;
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
 import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode.ChildAddressabilitySummary;
+import org.opendaylight.mdsal.binding.dom.codec.api.CommonDataObjectCodecTreeNode.ChildAddressabilitySummary;
 import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.DataRoot;
-import org.opendaylight.yangtools.yang.binding.Identifiable;
+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.yang.binding.InstanceIdentifier.Item;
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-final class DataContainerCodecPrototype<T extends WithStatus> implements NodeContextSupplier {
+abstract sealed class DataContainerCodecPrototype<T extends RuntimeTypeContainer> implements NodeContextSupplier
+        permits AugmentationCodecPrototype, DataObjectCodecPrototype {
     private static final Logger LOG = LoggerFactory.getLogger(DataContainerCodecPrototype.class);
 
-    private final T schema;
-    private final QNameModule namespace;
-    private final CodecContextFactory factory;
-    private final Item<?> bindingArg;
-    private final PathArgument yangArg;
-    private final ChildAddressabilitySummary childAddressabilitySummary;
-
-    private volatile DataContainerCodecContext<?, T> instance;
+    private static final VarHandle INSTANCE;
 
-    @SuppressWarnings("unchecked")
-    private DataContainerCodecPrototype(final Class<?> cls, final PathArgument arg, final T nodeSchema,
-            final CodecContextFactory factory) {
-        this(Item.of((Class<? extends DataObject>) cls), arg, nodeSchema, factory);
+    static {
+        try {
+            INSTANCE = MethodHandles.lookup().findVarHandle(DataContainerCodecPrototype.class,
+                "instance", DataContainerCodecContext.class);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new ExceptionInInitializerError(e);
+        }
     }
 
-    private DataContainerCodecPrototype(final Item<?> bindingArg, final PathArgument arg, final T nodeSchema,
+    private final @NonNull T type;
+    private final @NonNull QNameModule namespace;
+    private final @NonNull CodecContextFactory factory;
+    private final @NonNull Item<?> bindingArg;
+    private final @NonNull ChildAddressabilitySummary childAddressabilitySummary;
+
+    // multiple paths represent augmentation wrapper
+    // FIXME: this means it is either this or 'childArgs'
+
+    // Accessed via INSTANCE
+    @SuppressWarnings("unused")
+    private volatile DataContainerCodecContext<?, T> instance;
+
+    DataContainerCodecPrototype(final Item<?> bindingArg, final QNameModule namespace, final T type,
             final CodecContextFactory factory) {
-        this.bindingArg = bindingArg;
-        this.yangArg = arg;
-        this.schema = nodeSchema;
-        this.factory = factory;
-
-        if (arg instanceof AugmentationIdentifier) {
-            this.namespace = Iterables.getFirst(((AugmentationIdentifier) arg).getPossibleChildNames(), null)
-                    .getModule();
-        } else {
-            this.namespace = arg.getNodeType().getModule();
-        }
+        this.bindingArg = requireNonNull(bindingArg);
+        this.namespace = requireNonNull(namespace);
+        this.type = requireNonNull(type);
+        this.factory = requireNonNull(factory);
 
-        this.childAddressabilitySummary = computeChildAddressabilitySummary(nodeSchema);
+        childAddressabilitySummary = type instanceof RuntimeType runtimeType
+            ? computeChildAddressabilitySummary(runtimeType.statement())
+                // BindingRuntimeTypes, does not matter
+                : ChildAddressabilitySummary.MIXED;
     }
 
-    private static ChildAddressabilitySummary computeChildAddressabilitySummary(final WithStatus nodeSchema) {
-        if (nodeSchema instanceof DataNodeContainer) {
+    private static @NonNull ChildAddressabilitySummary computeChildAddressabilitySummary(final Object nodeSchema) {
+        // FIXME: rework this to work on EffectiveStatements
+        if (nodeSchema instanceof DataNodeContainer contaner) {
             boolean haveAddressable = false;
             boolean haveUnaddressable = false;
-            for (DataSchemaNode child : ((DataNodeContainer) nodeSchema).getChildNodes()) {
+            for (DataSchemaNode child : contaner.getChildNodes()) {
                 if (child instanceof ContainerSchemaNode || child instanceof AugmentationSchemaNode) {
                     haveAddressable = true;
-                } else if (child instanceof ListSchemaNode) {
-                    if (((ListSchemaNode) child).getKeyDefinition().isEmpty()) {
+                } else if (child instanceof ListSchemaNode list) {
+                    if (list.getKeyDefinition().isEmpty()) {
                         haveUnaddressable = true;
                     } else {
                         haveAddressable = true;
                     }
-                } else if (child instanceof AnyDataSchemaNode || child instanceof AnyXmlSchemaNode
+                } else if (child instanceof AnydataSchemaNode || child instanceof AnyxmlSchemaNode
                         || child instanceof TypedDataSchemaNode) {
                     haveUnaddressable = true;
-                } else if (child instanceof ChoiceSchemaNode) {
-                    switch (computeChildAddressabilitySummary(child)) {
-                        case ADDRESSABLE:
+                } else if (child instanceof ChoiceSchemaNode choice) {
+                    switch (computeChildAddressabilitySummary(choice)) {
+                        case ADDRESSABLE -> haveAddressable = true;
+                        case UNADDRESSABLE -> haveUnaddressable = true;
+                        case MIXED -> {
                             haveAddressable = true;
-                            break;
-                        case MIXED:
-                            haveAddressable = true;
-                            haveUnaddressable = true;
-                            break;
-                        case UNADDRESSABLE:
                             haveUnaddressable = true;
-                            break;
-                        default:
-                            throw new IllegalStateException("Unhandled accessibility summary for " + child);
+                        }
+                        default -> throw new IllegalStateException("Unhandled accessibility summary for " + child);
                     }
                 } else {
                     LOG.warn("Unhandled child node {}", child);
@@ -113,131 +114,103 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
             }
 
             return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
-        } else if (nodeSchema instanceof ChoiceSchemaNode) {
-            boolean haveAddressable = false;
-            boolean haveUnaddressable = false;
-            for (CaseSchemaNode child : ((ChoiceSchemaNode) nodeSchema).getCases().values()) {
-                switch (computeChildAddressabilitySummary(child)) {
-                    case ADDRESSABLE:
-                        haveAddressable = true;
-                        break;
-                    case UNADDRESSABLE:
-                        haveUnaddressable = true;
-                        break;
-                    case MIXED:
-                        // A child is mixed, which means we are mixed, too
-                        return ChildAddressabilitySummary.MIXED;
-                    default:
-                        throw new IllegalStateException("Unhandled accessibility summary for " + child);
-                }
-            }
-
-            if (!haveAddressable) {
-                // Empty or all are unaddressable
-                return ChildAddressabilitySummary.UNADDRESSABLE;
-            }
-
-            return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
+        } else if (nodeSchema instanceof ChoiceSchemaNode choice) {
+            return computeChildAddressabilitySummary(choice);
         }
 
         // No child nodes possible: return unaddressable
         return ChildAddressabilitySummary.UNADDRESSABLE;
     }
 
-    static DataContainerCodecPrototype<SchemaContext> rootPrototype(final CodecContextFactory factory) {
-        final SchemaContext schema = factory.getRuntimeContext().getSchemaContext();
-        final NodeIdentifier arg = NodeIdentifier.create(schema.getQName());
-        return new DataContainerCodecPrototype<>(DataRoot.class, arg, schema, factory);
-    }
+    private static @NonNull ChildAddressabilitySummary computeChildAddressabilitySummary(
+            final ChoiceSchemaNode choice) {
+        boolean haveAddressable = false;
+        boolean haveUnaddressable = false;
+        for (CaseSchemaNode child : choice.getCases()) {
+            switch (computeChildAddressabilitySummary(child)) {
+                case ADDRESSABLE:
+                    haveAddressable = true;
+                    break;
+                case UNADDRESSABLE:
+                    haveUnaddressable = true;
+                    break;
+                case MIXED:
+                    // A child is mixed, which means we are mixed, too
+                    return ChildAddressabilitySummary.MIXED;
+                default:
+                    throw new IllegalStateException("Unhandled accessibility summary for " + child);
+            }
+        }
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    static <T extends DataSchemaNode> DataContainerCodecPrototype<T> from(final Class<?> cls, final T schema,
-            final CodecContextFactory factory) {
-        return new DataContainerCodecPrototype(cls, NodeIdentifier.create(schema.getQName()), schema, factory);
+        if (!haveAddressable) {
+            // Empty or all are unaddressable
+            return ChildAddressabilitySummary.UNADDRESSABLE;
+        }
+
+        return haveUnaddressable ? ChildAddressabilitySummary.MIXED : ChildAddressabilitySummary.ADDRESSABLE;
     }
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    static <T extends DataSchemaNode> DataContainerCodecPrototype<T> from(final Item<?> bindingArg, final T schema,
+    static <T extends CompositeRuntimeType> DataContainerCodecPrototype<T> from(final Class<?> cls, final T type,
             final CodecContextFactory factory) {
-        return new DataContainerCodecPrototype(bindingArg, NodeIdentifier.create(schema.getQName()), schema, factory);
+        return new DataObjectCodecPrototype<>(cls, createIdentifier(type), type, factory);
     }
 
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    static DataContainerCodecPrototype<?> from(final Class<?> augClass, final AugmentationIdentifier arg,
-            final AugmentationSchemaNode schema, final CodecContextFactory factory) {
-        return new DataContainerCodecPrototype(augClass, arg, schema, factory);
+    static <T extends CompositeRuntimeType> DataContainerCodecPrototype<T> from(final Item<?> bindingArg, final T type,
+            final CodecContextFactory factory) {
+        return new DataObjectCodecPrototype<>(bindingArg, createIdentifier(type), type, factory);
     }
 
-    static DataContainerCodecPrototype<NotificationDefinition> from(final Class<?> augClass,
-            final NotificationDefinition schema, final CodecContextFactory factory) {
-        final PathArgument arg = NodeIdentifier.create(schema.getQName());
-        return new DataContainerCodecPrototype<>(augClass,arg, schema, factory);
+    private static @NonNull NodeIdentifier createIdentifier(final CompositeRuntimeType type) {
+        final Object arg = type.statement().argument();
+        verify(arg instanceof QName, "Unexpected type %s argument %s", type, arg);
+        return NodeIdentifier.create((QName) arg);
     }
 
-    protected T getSchema() {
-        return schema;
+    final @NonNull T getType() {
+        return type;
     }
 
-    ChildAddressabilitySummary getChildAddressabilitySummary() {
+    final @NonNull ChildAddressabilitySummary getChildAddressabilitySummary() {
         return childAddressabilitySummary;
     }
 
-    protected QNameModule getNamespace() {
+    final @NonNull QNameModule getNamespace() {
         return namespace;
     }
 
-    protected CodecContextFactory getFactory() {
+    final @NonNull CodecContextFactory getFactory() {
         return factory;
     }
 
-    protected Class<?> getBindingClass() {
+    final @NonNull Class<?> getBindingClass() {
         return bindingArg.getType();
     }
 
-    protected Item<?> getBindingArg() {
+    final @NonNull Item<?> getBindingArg() {
         return bindingArg;
     }
 
-    protected PathArgument getYangArg() {
-        return yangArg;
-    }
+    abstract @NonNull NodeIdentifier getYangArg();
 
     @Override
-    public DataContainerCodecContext<?, T> get() {
-        DataContainerCodecContext<?, T> tmp = instance;
-        if (tmp == null) {
-            synchronized (this) {
-                tmp = instance;
-                if (tmp == null) {
-                    instance = tmp = createInstance();
-                }
-            }
-        }
+    public final DataContainerCodecContext<?, T> get() {
+        final var existing = (DataContainerCodecContext<?, T>) INSTANCE.getAcquire(this);
+        return existing != null ? existing : loadInstance();
+    }
 
-        return tmp;
-    }
-
-    @Holding("this")
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    private @NonNull DataContainerCodecContext<?, T> createInstance() {
-        // FIXME: make protected abstract
-        if (schema instanceof ContainerSchemaNode) {
-            return new ContainerNodeCodecContext(this);
-        } else if (schema instanceof ListSchemaNode) {
-            return Identifiable.class.isAssignableFrom(getBindingClass())
-                    ? KeyedListNodeCodecContext.create((DataContainerCodecPrototype<ListSchemaNode>) this)
-                            : new ListNodeCodecContext(this);
-        } else if (schema instanceof ChoiceSchemaNode) {
-            return new ChoiceNodeCodecContext(this);
-        } else if (schema instanceof AugmentationSchemaNode) {
-            return new AugmentationNodeContext(this);
-        } else if (schema instanceof CaseSchemaNode) {
-            return new CaseNodeCodecContext(this);
-        }
-        throw new IllegalArgumentException("Unsupported type " + getBindingClass() + " " + schema);
+    @SuppressWarnings("unchecked")
+    final <R extends CompositeRuntimeType> DataObjectCodecContext<?, R> getDataObject() {
+        final var context = get();
+        verify(context instanceof DataObjectCodecContext, "Unexpected instance %s", context);
+        return (DataObjectCodecContext<?, R>) context;
     }
 
-    boolean isChoice() {
-        return schema instanceof ChoiceSchemaNode;
+    private @NonNull DataContainerCodecContext<?, T> loadInstance() {
+        final var tmp = createInstance();
+        final var witness = (DataContainerCodecContext<?, T>) INSTANCE.compareAndExchangeRelease(this, null, tmp);
+        return witness == null ? tmp : witness;
     }
+
+    // This method must allow concurrent loading, i.e. nothing in it may have effects outside of the loaded object
+    abstract @NonNull DataContainerCodecContext<?, T> createInstance();
 }