Bump upstreams
[mdsal.git] / binding / mdsal-binding-dom-adapter / src / main / java / org / opendaylight / mdsal / binding / dom / adapter / AbstractDataObjectModification.java
index e0672ae9db0f47d1e8445152fc4ab3b382fedc5b..57ab7c906ece3620e00d1c69d0fc09224df80d92 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.mdsal.binding.dom.adapter;
 
+import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 import static org.opendaylight.yangtools.yang.data.tree.api.ModificationType.UNMODIFIED;
 
@@ -15,10 +16,12 @@ import com.google.common.base.MoreObjects.ToStringHelper;
 import com.google.common.base.VerifyException;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import java.util.Optional;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.eclipse.jdt.annotation.NonNull;
@@ -31,11 +34,11 @@ import org.opendaylight.yangtools.yang.binding.Augmentation;
 import org.opendaylight.yangtools.yang.binding.ChildOf;
 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
 import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.Identifiable;
-import org.opendaylight.yangtools.yang.binding.Identifier;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.binding.Key;
+import org.opendaylight.yangtools.yang.binding.KeyAware;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
@@ -58,15 +61,43 @@ abstract sealed class AbstractDataObjectModification<T extends DataObject, N ext
         implements DataObjectModification<T>
         permits LazyAugmentationModification, LazyDataObjectModification {
     private static final Logger LOG = LoggerFactory.getLogger(AbstractDataObjectModification.class);
+    private static final @NonNull Object NULL_DATA_OBJECT = new Object();
+    private static final VarHandle MODIFICATION_TYPE;
+    private static final VarHandle MODIFIED_CHILDREN;
+    private static final VarHandle DATA_BEFORE;
+    private static final VarHandle DATA_AFTER;
+
+    static {
+        final var lookup = MethodHandles.lookup();
+
+        try {
+            MODIFICATION_TYPE = lookup.findVarHandle(AbstractDataObjectModification.class, "modificationType",
+                ModificationType.class);
+            MODIFIED_CHILDREN = lookup.findVarHandle(AbstractDataObjectModification.class, "modifiedChildren",
+                ImmutableList.class);
+            DATA_BEFORE = lookup.findVarHandle(AbstractDataObjectModification.class, "dataBefore", Object.class);
+            DATA_AFTER = lookup.findVarHandle(AbstractDataObjectModification.class, "dataAfter", Object.class);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
 
     final @NonNull DataTreeCandidateNode domData;
     final @NonNull PathArgument identifier;
     final @NonNull N codec;
 
-    private volatile ImmutableList<AbstractDataObjectModification<?, ?>> childNodesCache;
+    @SuppressWarnings("unused")
+    @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
+    private volatile ImmutableList<AbstractDataObjectModification<?, ?>> modifiedChildren;
+    @SuppressWarnings("unused")
+    @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
     private volatile ModificationType modificationType;
-    private volatile @Nullable T dataBefore;
-    private volatile @Nullable T dataAfter;
+    @SuppressWarnings("unused")
+    @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
+    private volatile Object dataBefore;
+    @SuppressWarnings("unused")
+    @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
+    private volatile Object dataAfter;
 
     AbstractDataObjectModification(final DataTreeCandidateNode domData, final N codec, final PathArgument identifier) {
         this.domData = requireNonNull(domData);
@@ -97,13 +128,13 @@ abstract sealed class AbstractDataObjectModification<T extends DataObject, N ext
 
     @Override
     public final ModificationType getModificationType() {
-        var localType = modificationType;
-        if (localType != null) {
-            return localType;
-        }
+        final var local = (ModificationType) MODIFICATION_TYPE.getAcquire(this);
+        return local != null ? local : loadModificationType();
+    }
 
+    private @NonNull ModificationType loadModificationType() {
         final var domModificationType = domModificationType();
-        modificationType = localType = switch (domModificationType) {
+        final var computed = switch (domModificationType) {
             case APPEARED, WRITE -> ModificationType.WRITE;
             case DISAPPEARED, DELETE -> ModificationType.DELETE;
             case SUBTREE_MODIFIED -> resolveSubtreeModificationType();
@@ -111,29 +142,46 @@ abstract sealed class AbstractDataObjectModification<T extends DataObject, N ext
                 // TODO: Should we lie about modification type instead of exception?
                 throw new IllegalStateException("Unsupported DOM Modification type " + domModificationType);
         };
-        return localType;
+
+        MODIFICATION_TYPE.setRelease(this, computed);
+        return computed;
     }
 
     @Override
     public final T getDataBefore() {
-        var local = dataBefore;
-        if (local == null) {
-            dataBefore = local = deserialize(domData.getDataBefore());
-        }
-        return local;
+        final var local = DATA_BEFORE.getAcquire(this);
+        return local != null ? unmask(local) : loadDataBefore();
+    }
+
+    private @Nullable T loadDataBefore() {
+        final var computed = deserializeNullable(domData.dataBefore());
+        final var witness = DATA_BEFORE.compareAndExchangeRelease(this, null, mask(computed));
+        return witness == null ? computed : unmask(witness);
     }
 
     @Override
     public final T getDataAfter() {
-        var local = dataAfter;
-        if (local == null) {
-            dataAfter = local = deserialize(domData.getDataAfter());
-        }
-        return local;
+        final var local = DATA_AFTER.getAcquire(this);
+        return local != null ? unmask(local) : loadDataAfter();
+    }
+
+    private @Nullable T loadDataAfter() {
+        final var computed = deserializeNullable(domData.dataAfter());
+        final var witness = DATA_AFTER.compareAndExchangeRelease(this, null, mask(computed));
+        return witness == null ? computed : unmask(witness);
+    }
+
+    private static <T extends DataObject> @NonNull Object mask(final @Nullable T obj) {
+        return obj != null ? obj : NULL_DATA_OBJECT;
+    }
+
+    @SuppressWarnings("unchecked")
+    private @Nullable T unmask(final @NonNull Object obj) {
+        return obj == NULL_DATA_OBJECT ? null : (T) verifyNotNull(obj);
     }
 
-    private @Nullable T deserialize(final Optional<NormalizedNode> normalized) {
-        return normalized.isEmpty() ? null : deserialize(normalized.orElseThrow());
+    private @Nullable T deserializeNullable(final @Nullable NormalizedNode normalized) {
+        return normalized == null ? null : deserialize(normalized);
     }
 
     abstract @Nullable T deserialize(@NonNull NormalizedNode normalized);
@@ -148,10 +196,10 @@ abstract sealed class AbstractDataObjectModification<T extends DataObject, N ext
         var current = toEnter.hasNext() ? firstModifiedChild(toEnter.next()) : domData;
         // ... and for everything else we can just go wild
         while (toEnter.hasNext() && current != null) {
-            current = current.getModifiedChild(toEnter.next()).orElse(null);
+            current = current.modifiedChild(toEnter.next());
         }
 
-        if (current == null || current.getModificationType() == UNMODIFIED) {
+        if (current == null || current.modificationType() == UNMODIFIED) {
             return null;
         }
         return from(childCodec, current);
@@ -161,11 +209,8 @@ abstract sealed class AbstractDataObjectModification<T extends DataObject, N ext
 
     @Override
     public final ImmutableList<AbstractDataObjectModification<?, ?>> getModifiedChildren() {
-        var local = childNodesCache;
-        if (local == null) {
-            childNodesCache = local = createModifiedChilden(codec, domData, domChildNodes());
-        }
-        return local;
+        final var local = (ImmutableList<AbstractDataObjectModification<?, ?>>) MODIFIED_CHILDREN.getAcquire(this);
+        return local != null ? local : loadModifiedChilden();
     }
 
     @Override
@@ -182,6 +227,16 @@ abstract sealed class AbstractDataObjectModification<T extends DataObject, N ext
             .collect(Collectors.toList());
     }
 
+    @SuppressWarnings("unchecked")
+    private @NonNull ImmutableList<AbstractDataObjectModification<?, ?>> loadModifiedChilden() {
+        final var builder = ImmutableList.<AbstractDataObjectModification<?, ?>>builder();
+        populateList(builder, codec, domData, domChildNodes());
+        final var computed = builder.build();
+        // Non-trivial return: use CAS to ensure we reuse concurrent loads
+        final var witness = MODIFIED_CHILDREN.compareAndExchangeRelease(this, null, computed);
+        return witness == null ? computed : (ImmutableList<AbstractDataObjectModification<?, ?>>) witness;
+    }
+
     @SuppressWarnings("unchecked")
     private <C extends DataObject> Stream<LazyDataObjectModification<C>> streamModifiedChildren(
             final Class<C> childType) {
@@ -192,15 +247,15 @@ abstract sealed class AbstractDataObjectModification<T extends DataObject, N ext
 
     @Override
     @SuppressWarnings("unchecked")
-    public final <C extends Identifiable<K> & ChildOf<? super T>, K extends Identifier<C>> DataObjectModification<C>
+    public final <C extends KeyAware<K> & ChildOf<? super T>, K extends Key<C>> DataObjectModification<C>
             getModifiedChildListItem(final Class<C> listItem, final K listKey) {
         return (DataObjectModification<C>) getModifiedChild(IdentifiableItem.of(listItem, listKey));
     }
 
     @Override
     @SuppressWarnings("unchecked")
-    public final <H extends ChoiceIn<? super T> & DataObject, C extends Identifiable<K> & ChildOf<? super H>,
-            K extends Identifier<C>> DataObjectModification<C> getModifiedChildListItem(final Class<H> caseType,
+    public final <H extends ChoiceIn<? super T> & DataObject, C extends KeyAware<K> & ChildOf<? super H>,
+            K extends Key<C>> DataObjectModification<C> getModifiedChildListItem(final Class<H> caseType,
                     final Class<C> listItem, final K listKey) {
         return (DataObjectModification<C>) getModifiedChild(IdentifiableItem.of(caseType, listItem, listKey));
     }
@@ -252,7 +307,7 @@ abstract sealed class AbstractDataObjectModification<T extends DataObject, N ext
                 // is the case, we need to turn this modification into a WRITE operation, so that the user is able
                 // to observe those nodes being introduced. This is not efficient, but unfortunately unavoidable,
                 // as we cannot accurately represent such changes.
-                for (DataTreeCandidateNode child : domChildNodes()) {
+                for (var child : domChildNodes()) {
                     if (BindingStructuralType.recursiveFrom(child) == BindingStructuralType.NOT_ADDRESSABLE) {
                         // We have a non-addressable child, turn this modification into a write
                         yield ModificationType.WRITE;
@@ -265,22 +320,14 @@ abstract sealed class AbstractDataObjectModification<T extends DataObject, N ext
         };
     }
 
-    private static @NonNull ImmutableList<AbstractDataObjectModification<?, ?>> createModifiedChilden(
-            final CommonDataObjectCodecTreeNode<?> parentCodec, final DataTreeCandidateNode parent,
-            final Collection<DataTreeCandidateNode> children) {
-        final var result = ImmutableList.<AbstractDataObjectModification<?, ?>>builder();
-        populateList(result, parentCodec, parent, children);
-        return result.build();
-    }
-
     private static void populateList(final ImmutableList.Builder<AbstractDataObjectModification<?, ?>> result,
             final CommonDataObjectCodecTreeNode<?> parentCodec, final DataTreeCandidateNode parent,
             final Collection<DataTreeCandidateNode> children) {
         final var augmentChildren =
             ArrayListMultimap.<BindingAugmentationCodecTreeNode<?>, DataTreeCandidateNode>create();
 
-        for (var domChildNode : parent.getChildNodes()) {
-            if (domChildNode.getModificationType() != UNMODIFIED) {
+        for (var domChildNode : parent.childNodes()) {
+            if (domChildNode.modificationType() != UNMODIFIED) {
                 final var type = BindingStructuralType.from(domChildNode);
                 if (type != BindingStructuralType.NOT_ADDRESSABLE) {
                     /*
@@ -288,7 +335,7 @@ abstract sealed class AbstractDataObjectModification<T extends DataObject, N ext
                      * We will use that type to further specify debug log.
                      */
                     try {
-                        final var childCodec = parentCodec.yangPathArgumentChild(domChildNode.getIdentifier());
+                        final var childCodec = parentCodec.yangPathArgumentChild(domChildNode.name());
                         if (childCodec instanceof BindingDataObjectCodecTreeNode<?> childDataObjectCodec) {
                             populateList(result, type, childDataObjectCodec, domChildNode);
                         } else if (childCodec instanceof BindingAugmentationCodecTreeNode<?> childAugmentationCodec) {
@@ -323,10 +370,10 @@ abstract sealed class AbstractDataObjectModification<T extends DataObject, N ext
         switch (type) {
             case INVISIBLE_LIST:
                 // We use parent codec intentionally.
-                populateListWithSingleCodec(result, childCodec, domChildNode.getChildNodes());
+                populateListWithSingleCodec(result, childCodec, domChildNode.childNodes());
                 break;
             case INVISIBLE_CONTAINER:
-                populateList(result, childCodec, domChildNode, domChildNode.getChildNodes());
+                populateList(result, childCodec, domChildNode, domChildNode.childNodes());
                 break;
             case UNKNOWN:
             case VISIBLE_CONTAINER:
@@ -340,7 +387,7 @@ abstract sealed class AbstractDataObjectModification<T extends DataObject, N ext
             final ImmutableList.Builder<AbstractDataObjectModification<?, ?>> result,
             final BindingDataObjectCodecTreeNode<?> codec, final Collection<DataTreeCandidateNode> childNodes) {
         for (var child : childNodes) {
-            if (child.getModificationType() != UNMODIFIED) {
+            if (child.modificationType() != UNMODIFIED) {
                 result.add(new LazyDataObjectModification<>(codec, child));
             }
         }