Drop a FIXME in DataContainerCodecPrototype
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / DataContainerCodecPrototype.java
index 5ee1df3daca2ca38d10503d06f89c74dc5502c8b..73b3c577b982bc6135e13ae8303a6cbebcf643ca 100644 (file)
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
 import com.google.common.collect.Iterables;
-import javax.annotation.concurrent.GuardedBy;
+import org.checkerframework.checker.lock.qual.Holding;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode.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.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
 import org.opendaylight.yangtools.yang.common.QNameModule;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 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.AugmentationSchema;
-import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+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> implements NodeContextSupplier {
+final class DataContainerCodecPrototype<T extends WithStatus> implements NodeContextSupplier {
+    private static final Logger LOG = LoggerFactory.getLogger(DataContainerCodecPrototype.class);
 
     private final T schema;
     private final QNameModule namespace;
     private final CodecContextFactory factory;
-    private final Class<?> bindingClass;
-    private final InstanceIdentifier.Item<?> bindingArg;
-    private final YangInstanceIdentifier.PathArgument yangArg;
-    private volatile DataContainerCodecContext<?,T> instance = null;
+    private final Item<?> bindingArg;
+    private final PathArgument yangArg;
+    private final ChildAddressabilitySummary childAddressabilitySummary;
 
-    @SuppressWarnings({"rawtypes", "unchecked"})
-    private DataContainerCodecPrototype(final Class<?> cls, final YangInstanceIdentifier.PathArgument arg, final T nodeSchema,
+    /*
+     * FIXME: This field is now utterly in the hot path of CodecDataObject's instantiation of data. It is volatile
+     *        support double-checked locking, as detailed in
+     *        https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java.
+     *
+     *        Removing volatile here would seem to be worthwhile, as it would mean that in the initialized case we end
+     *        up with no happens-before synchronization between threads, allowing JIT to perform better.
+     *
+     *        Since we are on Java 5+, DataContainerCodecContext and its subclasses play a role here. All of their
+     *        fields are either final or volatile, so intuition would indicate they themselves could (already?) be
+     *        acting in the same capacity as "FinalWrapper" in the above example does -- in which case we can safely
+     *        remove the volatile access altogether, after documenting this fact there (and potentially placing some
+     *        guards).
+     *
+     *        The presence of volatile fields seems to violate this, as
+     *        http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html mentions "such that all of the
+     *        fields of Helper are final".
+     *
+     *        There are only two violations, both relating to dynamic discovery of augmentations, which should be
+     *        eliminated once we have constant class loader visibility (and thus can discover all valid augmentations
+     *        and aliases). Alternatively, adding an indirection could be considered after the effects are fully
+     *        understood.
+     *
+     *        Alternatively we can consider the use of VarHandle acquire/release mechanics to remove this instance
+     *        from global synchronization order -- that would be a Java 9+ concern, though.
+     *
+     *        Finally, the benefits of addressing this are masked by CodecDataObject operating on volatiles, hence
+     *        already doing a barrier very close to us -- thus reducing the scope in which code can be reordered.
+     *        If CodecDataObject moves to VarHandles and uses acquire/release there, we will be put on the spot here,
+     *        though.
+     */
+    private volatile DataContainerCodecContext<?, T> instance;
+
+    @SuppressWarnings("unchecked")
+    private DataContainerCodecPrototype(final Class<?> cls, final PathArgument arg, final T nodeSchema,
             final CodecContextFactory factory) {
-        this.bindingClass = cls;
+        this(Item.of((Class<? extends DataObject>) cls), arg, nodeSchema, factory);
+    }
+
+    private DataContainerCodecPrototype(final Item<?> bindingArg, final PathArgument arg, final T nodeSchema,
+            final CodecContextFactory factory) {
+        this.bindingArg = bindingArg;
         this.yangArg = arg;
         this.schema = nodeSchema;
         this.factory = factory;
-        this.bindingArg = new InstanceIdentifier.Item(bindingClass);
 
         if (arg instanceof AugmentationIdentifier) {
-            this.namespace = Iterables.getFirst(((AugmentationIdentifier) arg).getPossibleChildNames(), null).getModule();
+            this.namespace = Iterables.getFirst(((AugmentationIdentifier) arg).getPossibleChildNames(), null)
+                    .getModule();
         } else {
             this.namespace = arg.getNodeType().getModule();
         }
+
+        this.childAddressabilitySummary = computeChildAddressabilitySummary(nodeSchema);
     }
 
-    @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);
+    private static ChildAddressabilitySummary computeChildAddressabilitySummary(final WithStatus nodeSchema) {
+        if (nodeSchema instanceof DataNodeContainer) {
+            boolean haveAddressable = false;
+            boolean haveUnaddressable = false;
+            for (DataSchemaNode child : ((DataNodeContainer) nodeSchema).getChildNodes()) {
+                if (child instanceof ContainerSchemaNode || child instanceof AugmentationSchemaNode) {
+                    haveAddressable = true;
+                } else if (child instanceof ListSchemaNode) {
+                    if (((ListSchemaNode) child).getKeyDefinition().isEmpty()) {
+                        haveUnaddressable = true;
+                    } else {
+                        haveAddressable = true;
+                    }
+                } else if (child instanceof AnyDataSchemaNode || child instanceof AnyXmlSchemaNode
+                        || child instanceof TypedDataSchemaNode) {
+                    haveUnaddressable = true;
+                } else if (child instanceof ChoiceSchemaNode) {
+                    switch (computeChildAddressabilitySummary(child)) {
+                        case ADDRESSABLE:
+                            haveAddressable = true;
+                            break;
+                        case MIXED:
+                            haveAddressable = true;
+                            haveUnaddressable = true;
+                            break;
+                        case UNADDRESSABLE:
+                            haveUnaddressable = true;
+                            break;
+                        default:
+                            throw new IllegalStateException("Unhandled accessibility summary for " + child);
+                    }
+                } else {
+                    LOG.warn("Unhandled child node {}", child);
+                }
+            }
+
+            if (!haveAddressable) {
+                // Empty or all are unaddressable
+                return ChildAddressabilitySummary.UNADDRESSABLE;
+            }
+
+            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;
+        }
+
+        // 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<SchemaContext>(DataRoot.class, arg, schema, factory);
+        return new DataContainerCodecPrototype<>(DataRoot.class, arg, schema, factory);
+    }
+
+    @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);
     }
 
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    static <T extends DataSchemaNode> DataContainerCodecPrototype<T> from(final Item<?> bindingArg, final T schema,
+            final CodecContextFactory factory) {
+        return new DataContainerCodecPrototype(bindingArg, NodeIdentifier.create(schema.getQName()), schema, factory);
+    }
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
     static DataContainerCodecPrototype<?> from(final Class<?> augClass, final AugmentationIdentifier arg,
-            final AugmentationSchema schema, final CodecContextFactory factory) {
+            final AugmentationSchemaNode schema, final CodecContextFactory factory) {
         return new DataContainerCodecPrototype(augClass, arg, schema, factory);
     }
 
-    static DataContainerCodecPrototype<NotificationDefinition> from(final Class<?> augClass, final NotificationDefinition schema, final CodecContextFactory factory) {
+    static DataContainerCodecPrototype<NotificationDefinition> from(final Class<?> augClass,
+            final NotificationDefinition schema, final CodecContextFactory factory) {
         final PathArgument arg = NodeIdentifier.create(schema.getQName());
-        return new DataContainerCodecPrototype<NotificationDefinition>(augClass,arg, schema, factory);
+        return new DataContainerCodecPrototype<>(augClass,arg, schema, factory);
     }
 
     protected T getSchema() {
         return schema;
     }
 
+    ChildAddressabilitySummary getChildAddressabilitySummary() {
+        return childAddressabilitySummary;
+    }
+
     protected QNameModule getNamespace() {
         return namespace;
     }
@@ -90,26 +222,25 @@ final class DataContainerCodecPrototype<T> implements NodeContextSupplier {
     }
 
     protected Class<?> getBindingClass() {
-        return bindingClass;
+        return bindingArg.getType();
     }
 
-    protected InstanceIdentifier.Item<?> getBindingArg() {
+    protected Item<?> getBindingArg() {
         return bindingArg;
     }
 
-    protected YangInstanceIdentifier.PathArgument getYangArg() {
+    protected PathArgument getYangArg() {
         return yangArg;
     }
 
     @Override
-    public DataContainerCodecContext<?,T> get() {
-        DataContainerCodecContext<?,T> tmp = instance;
+    public DataContainerCodecContext<?, T> get() {
+        DataContainerCodecContext<?, T> tmp = instance;
         if (tmp == null) {
             synchronized (this) {
                 tmp = instance;
                 if (tmp == null) {
-                    tmp = createInstance();
-                    instance = tmp;
+                    instance = tmp = createInstance();
                 }
             }
         }
@@ -117,26 +248,24 @@ final class DataContainerCodecPrototype<T> implements NodeContextSupplier {
         return tmp;
     }
 
-    @GuardedBy("this")
+    @Holding("this")
     @SuppressWarnings({ "rawtypes", "unchecked" })
-    private DataContainerCodecContext<?,T> createInstance() {
+    private @NonNull DataContainerCodecContext<?, T> createInstance() {
         // FIXME: make protected abstract
         if (schema instanceof ContainerSchemaNode) {
             return new ContainerNodeCodecContext(this);
         } else if (schema instanceof ListSchemaNode) {
-            if (Identifiable.class.isAssignableFrom(getBindingClass())) {
-                return new KeyedListNodeCodecContext(this);
-            } else {
-                return new ListNodeCodecContext(this);
-            }
+            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 AugmentationSchema) {
+        } else if (schema instanceof AugmentationSchemaNode) {
             return new AugmentationNodeContext(this);
-        } else if (schema instanceof ChoiceCaseNode) {
+        } else if (schema instanceof CaseSchemaNode) {
             return new CaseNodeCodecContext(this);
         }
-        throw new IllegalArgumentException("Unsupported type " + bindingClass + " " + schema);
+        throw new IllegalArgumentException("Unsupported type " + getBindingClass() + " " + schema);
     }
 
     boolean isChoice() {