import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
-import java.util.Collection;
-import java.util.Map.Entry;
import java.util.Optional;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.tree.impl.node.TreeNode;
import org.opendaylight.yangtools.yang.data.tree.impl.node.Version;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-final class InMemoryDataTreeModification extends AbstractCursorAware implements CursorAwareDataTreeModification,
- EffectiveModelContextProvider {
+final class InMemoryDataTreeModification extends AbstractCursorAware implements CursorAwareDataTreeModification {
private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTreeModification.class);
- private final RootApplyStrategy strategyTree;
- private final InMemoryDataTreeSnapshot snapshot;
- private final ModifiedNode rootNode;
- private final Version version;
+ private static final byte STATE_OPEN = 0;
+ private static final byte STATE_SEALING = 1;
+ private static final byte STATE_SEALED = 2;
- private static final VarHandle SEALED;
+ private static final VarHandle STATE;
static {
try {
- SEALED = MethodHandles.lookup().findVarHandle(InMemoryDataTreeModification.class, "sealed", int.class);
- } catch (ReflectiveOperationException e) {
+ STATE = MethodHandles.lookup().findVarHandle(InMemoryDataTreeModification.class, "state", byte.class);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
- // All access needs to go through this handle
+ private final RootApplyStrategy strategyTree;
+ private final InMemoryDataTreeSnapshot snapshot;
+ private final ModifiedNode rootNode;
+ private final Version version;
+
+ // All access needs to go through STATE
@SuppressWarnings("unused")
- private volatile int sealed;
+ @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749")
+ private volatile byte state;
InMemoryDataTreeModification(final InMemoryDataTreeSnapshot snapshot,
final RootApplyStrategy resolver) {
* node in modification and in data tree (if successfully
* committed) will be same and will not change.
*/
- version = snapshot.getRootNode().getSubtreeVersion().next();
+ version = snapshot.getRootNode().subtreeVersion().next();
}
ModifiedNode getRootModification() {
}
ModificationApplyOperation getStrategy() {
- final ModificationApplyOperation ret = strategyTree.delegate();
+ final var ret = strategyTree.delegate();
if (ret == null) {
throw new IllegalStateException("Schema Context is not available.");
}
}
@Override
- public EffectiveModelContext getEffectiveModelContext() {
- return snapshot.getEffectiveModelContext();
+ public EffectiveModelContext modelContext() {
+ return snapshot.modelContext();
}
@Override
public void write(final YangInstanceIdentifier path, final NormalizedNode data) {
- checkSealed();
+ checkOpen();
checkIdentifierReferencesData(path, data);
resolveModificationFor(path).write(data);
}
@Override
public void merge(final YangInstanceIdentifier path, final NormalizedNode data) {
- checkSealed();
+ checkOpen();
checkIdentifierReferencesData(path, data);
resolveModificationFor(path).merge(data, version);
}
@Override
public void delete(final YangInstanceIdentifier path) {
- checkSealed();
-
+ checkOpen();
resolveModificationFor(path).delete();
}
* the requested path which has been modified. If no such node exists,
* we use the node itself.
*/
- final Entry<YangInstanceIdentifier, ModifiedNode> entry = StoreTreeNodes.findClosestsOrFirstMatch(rootNode,
- path, ModifiedNode.IS_TERMINAL_PREDICATE);
- final YangInstanceIdentifier key = entry.getKey();
- final ModifiedNode mod = entry.getValue();
-
- final Optional<? extends TreeNode> result = resolveSnapshot(key, mod);
- if (result.isPresent()) {
- final NormalizedNode data = result.orElseThrow().getData();
- return NormalizedNodes.findNode(key, data, path);
- }
-
- return Optional.empty();
+ final var terminal = StoreTreeNodes.findClosestsOrFirstMatch(rootNode, path,
+ input -> switch (input.getOperation()) {
+ case DELETE, MERGE, WRITE -> true;
+ case TOUCH, NONE -> false;
+ });
+ final var terminalPath = terminal.getKey();
+
+ final var result = resolveSnapshot(terminalPath, terminal.getValue());
+ return result == null ? Optional.empty() : NormalizedNodes.findNode(terminalPath, result.data(), path);
}
@SuppressWarnings("checkstyle:illegalCatch")
- private Optional<? extends TreeNode> resolveSnapshot(final YangInstanceIdentifier path,
- final ModifiedNode modification) {
- final Optional<? extends TreeNode> potentialSnapshot = modification.getSnapshot();
+ private @Nullable TreeNode resolveSnapshot(final YangInstanceIdentifier path, final ModifiedNode modification) {
+ final var potentialSnapshot = modification.getSnapshot();
if (potentialSnapshot != null) {
- return potentialSnapshot;
+ return potentialSnapshot.orElse(null);
}
try {
- return resolveModificationStrategy(path).apply(modification, modification.getOriginal(), version);
- } catch (final Exception e) {
+ return resolveModificationStrategy(path).apply(modification, modification.original(), version);
+ } catch (Exception e) {
LOG.error("Could not create snapshot for {}:{}", path, modification, e);
throw e;
}
* That is fine, as we will prune any empty TOUCH nodes in the last phase of the ready
* process.
*/
- ModificationApplyOperation operation = getStrategy();
- ModifiedNode modification = rootNode;
+ var operation = getStrategy();
+ var modification = rootNode;
int depth = 1;
- for (final PathArgument pathArg : path.getPathArguments()) {
+ for (var pathArg : path.getPathArguments()) {
operation = operation.childByArg(pathArg);
if (operation == null) {
throw new SchemaValidationFailedException(String.format("Child %s is not present in schema tree.",
return OperationWithModification.from(operation, modification);
}
- private void checkSealed() {
- checkState(!isSealed(), "Data Tree is sealed. No further modifications allowed.");
- }
-
@Override
public String toString() {
return "MutableDataTree [modification=" + rootNode + "]";
* We will use preallocated version, this means returned snapshot will
* have same version each time this method is called.
*/
- final TreeNode originalSnapshotRoot = snapshot.getRootNode();
- final Optional<? extends TreeNode> tempRoot = getStrategy().apply(rootNode, Optional.of(originalSnapshotRoot),
- version);
- checkState(tempRoot.isPresent(), "Data tree root is not present, possibly removed by previous modification");
-
- final InMemoryDataTreeSnapshot tempTree = new InMemoryDataTreeSnapshot(snapshot.getEffectiveModelContext(),
- tempRoot.orElseThrow(), strategyTree);
- return tempTree.newModification();
+ final var originalSnapshotRoot = snapshot.getRootNode();
+ final var newRoot = getStrategy().apply(rootNode, originalSnapshotRoot, version);
+ if (newRoot == null) {
+ throw new IllegalStateException("Data tree root is not present, possibly removed by previous modification");
+ }
+ return new InMemoryDataTreeSnapshot(snapshot.modelContext(), newRoot, strategyTree).newModification();
}
Version getVersion() {
boolean isSealed() {
// a quick check, synchronizes *only* on the sealed field
- return (int) SEALED.getAcquire(this) != 0;
+ return (byte) STATE.getAcquire(this) == STATE_SEALED;
+ }
+
+ private void checkOpen() {
+ final var local = (byte) STATE.getAcquire(this);
+ if (local != STATE_OPEN) {
+ throw new IllegalStateException("Data Tree is sealed. No further modifications allowed in state " + local);
+ }
}
private static void applyChildren(final DataTreeModificationCursor cursor, final ModifiedNode node) {
- final Collection<ModifiedNode> children = node.getChildren();
- if (!children.isEmpty()) {
+ if (!node.isEmpty()) {
cursor.enter(node.getIdentifier());
- for (final ModifiedNode child : children) {
+ for (var child : node.getChildren()) {
applyNode(cursor, child);
}
cursor.exit();
}
private static void applyNode(final DataTreeModificationCursor cursor, final ModifiedNode node) {
- switch (node.getOperation()) {
- case NONE:
- break;
- case DELETE:
- cursor.delete(node.getIdentifier());
- break;
- case MERGE:
+ final var operation = node.getOperation();
+ switch (operation) {
+ case NONE -> {
+ // No-op
+ }
+ case DELETE -> cursor.delete(node.getIdentifier());
+ case MERGE -> {
cursor.merge(node.getIdentifier(), node.getWrittenValue());
applyChildren(cursor, node);
- break;
- case TOUCH:
- // TODO: we could improve efficiency of cursor use if we could understand
- // nested TOUCH operations. One way of achieving that would be a proxy
- // cursor, which would keep track of consecutive enter and exit calls
- // and coalesce them.
+ }
+ case TOUCH -> {
+ // TODO: we could improve efficiency of cursor use if we could understand nested TOUCH operations. One
+ // way of achieving that would be a proxy cursor, which would keep track of consecutive enter and
+ // exit calls and coalesce them.
applyChildren(cursor, node);
- break;
- case WRITE:
+ }
+ case WRITE -> {
cursor.write(node.getIdentifier(), node.getWrittenValue());
applyChildren(cursor, node);
- break;
- default:
- throw new IllegalArgumentException("Unhandled node operation " + node.getOperation());
+ }
+ default -> throw new IllegalArgumentException("Unhandled node operation " + operation);
}
}
@Override
public void applyToCursor(final DataTreeModificationCursor cursor) {
- for (final ModifiedNode child : rootNode.getChildren()) {
+ for (var child : rootNode.getChildren()) {
applyNode(cursor, child);
}
}
private void checkIdentifierReferencesData(final YangInstanceIdentifier path,
final NormalizedNode data) {
final PathArgument arg;
-
if (!path.isEmpty()) {
arg = path.getLastPathArgument();
checkArgument(arg != null, "Instance identifier %s has invalid null path argument", path);
@Override
public Optional<DataTreeModificationCursor> openCursor(final YangInstanceIdentifier path) {
- final OperationWithModification op = resolveModificationFor(path);
+ final var op = resolveModificationFor(path);
return Optional.of(openCursor(new InMemoryDataTreeModificationCursor(this, path, op)));
}
@Override
public void ready() {
- // We want a full CAS with setVolatile() memory semantics, as we want to force happen-before
- // for everything, including whatever user code works.
- final boolean wasRunning = SEALED.compareAndSet(this, 0, 1);
- checkState(wasRunning, "Attempted to seal an already-sealed Data Tree.");
+ // We want a full CAS with setVolatile() memory semantics, as we want to force happen-before for everything,
+ // including whatever user code works.
+ if (!STATE.compareAndSet(this, STATE_OPEN, STATE_SEALING)) {
+ throw new IllegalStateException("Attempted to seal an already-sealed Data Tree.");
+ }
- AbstractReadyIterator current = AbstractReadyIterator.create(rootNode, getStrategy());
+ var current = AbstractReadyIterator.create(rootNode, getStrategy());
do {
current = current.process(version);
} while (current != null);
+
+ // Make sure all affects are visible before returning, as this object may be handed off to another thread, which
+ // needs to see any HashMap.modCount mutations completed. This is needed because isSealed() is now performing
+ // only the equivalent of an acquireFence()
+ STATE.setRelease(this, STATE_SEALED);
}
}