*/
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;
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;
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;
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);
@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();
// 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);
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);
@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
.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) {
@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));
}
// 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;
};
}
- 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) {
/*
* 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) {
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:
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));
}
}