/*
* Copyright (c) 2015, 2016 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.yangtools.yang.data.impl.schema.tree;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Verify;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
abstract class AbstractModifiedNodeBasedCandidateNode implements DataTreeCandidateNode {
private final ModifiedNode mod;
private final TreeNode newMeta;
private final TreeNode oldMeta;
protected AbstractModifiedNodeBasedCandidateNode(final ModifiedNode mod, final TreeNode oldMeta,
final TreeNode newMeta) {
this.newMeta = newMeta;
this.oldMeta = oldMeta;
this.mod = Preconditions.checkNotNull(mod);
}
protected final ModifiedNode getMod() {
return mod;
}
protected final TreeNode getNewMeta() {
return newMeta;
}
protected final TreeNode getOldMeta() {
return oldMeta;
}
private static TreeNode childMeta(final TreeNode parent, final PathArgument id) {
return parent == null ? null : parent.getChild(id).orNull();
}
private static boolean canHaveChildren(@Nullable final TreeNode oldMeta, @Nullable final TreeNode newMeta) {
if (oldMeta != null) {
return oldMeta.getData() instanceof NormalizedNodeContainer;
}
if (newMeta != null) {
return newMeta.getData() instanceof NormalizedNodeContainer;
}
return false;
}
@SuppressWarnings("unchecked")
private static NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> getContainer(@Nullable final TreeNode meta) {
return (meta == null ? null : (NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>>)meta.getData());
}
private ChildNode childNode(final ModifiedNode childMod) {
final PathArgument id = childMod.getIdentifier();
return new ChildNode(childMod, childMeta(oldMeta, id), childMeta(newMeta, id));
}
@Override
@Nonnull
public Collection<DataTreeCandidateNode> getChildNodes() {
switch (mod.getModificationType()) {
case APPEARED:
case DISAPPEARED:
case SUBTREE_MODIFIED:
return Collections2.transform(mod.getChildren(), this::childNode);
case UNMODIFIED:
// Unmodified node, but we still need to resolve potential children. canHaveChildren returns
// false if both arguments are null.
if (!canHaveChildren(oldMeta, newMeta)) {
return ImmutableList.of();
}
return Collections2.transform(getContainer(newMeta != null ? newMeta : oldMeta).getValue(),
AbstractRecursiveCandidateNode::unmodifiedNode);
case DELETE:
case WRITE:
// This is unusual, the user is requesting we follow into an otherwise-terminal node.
// We need to fudge things based on before/after data to correctly fake the expectations.
if (!canHaveChildren(oldMeta, newMeta)) {
return ImmutableList.of();
}
return AbstractDataTreeCandidateNode.deltaChildren(getContainer(oldMeta), getContainer(newMeta));
default:
throw new IllegalArgumentException("Unhandled modification type " + mod.getModificationType());
}
}
@Override
@Nonnull
public ModificationType getModificationType() {
return Verify.verifyNotNull(mod.getModificationType(), "Node %s does not have resolved modification type", mod);
}
private static Optional<NormalizedNode<?, ?>> optionalData(final TreeNode meta) {
return meta == null ? Optional.absent() : Optional.of(meta.getData());
}
@Override
@Nonnull
public final Optional<NormalizedNode<?, ?>> getDataAfter() {
return optionalData(newMeta);
}
@Override
@Nonnull
public final Optional<NormalizedNode<?, ?>> getDataBefore() {
return optionalData(oldMeta);
}
@Override
public final DataTreeCandidateNode getModifiedChild(final PathArgument identifier) {
switch (mod.getModificationType()) {
case APPEARED:
case DISAPPEARED:
case SUBTREE_MODIFIED:
final Optional<ModifiedNode> childMod = mod.getChild(identifier);
if (childMod.isPresent()) {
return childNode(childMod.get());
}
return null;
case UNMODIFIED:
if (!canHaveChildren(oldMeta, newMeta)) {
return null;
}
final Optional<NormalizedNode<?, ?>> maybeChild = getContainer(newMeta != null ? newMeta : oldMeta)
.getChild(identifier);
return maybeChild.isPresent() ? AbstractRecursiveCandidateNode.unmodifiedNode(maybeChild.get()) : null;
case DELETE:
case WRITE:
if (!canHaveChildren(oldMeta, newMeta)) {
return null;
}
return AbstractDataTreeCandidateNode.deltaChild(getContainer(oldMeta), getContainer(newMeta), identifier);
default:
throw new IllegalArgumentException("Unhandled modification type " + mod.getModificationType());
}
}
private static final class ChildNode extends AbstractModifiedNodeBasedCandidateNode {
ChildNode(final ModifiedNode mod, final TreeNode oldMeta, final TreeNode newMeta) {
super(mod, oldMeta, newMeta);
}
@Override
@Nonnull
public PathArgument getIdentifier() {
return getMod().getIdentifier();
}
}
@Override
public String toString() {
return this.getClass().getSimpleName() + "{mod = " + this.mod + ", oldMeta = " + this.oldMeta + ", newMeta = " +
this.newMeta + "}";
}
}