org.opendaylight.yangtools.yang.data.util.CompositeNodeDataWithSchema Maven / Gradle / Ivy
/*
* Copyright (c) 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.annotations.Beta;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.MetadataExtension;
import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ContainerLike;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
/**
* Utility class used for tracking parser state as needed by a StAX-like parser.
* This class is to be used only by respective XML and JSON parsers in yang-data-codec-xml and yang-data-codec-gson.
*
*
* Represents a node which is composed of multiple simpler nodes.
*/
public sealed class CompositeNodeDataWithSchema extends AbstractNodeDataWithSchema
permits AbstractMountPointDataWithSchema, CaseNodeDataWithSchema, ChoiceNodeDataWithSchema,
LeafListNodeDataWithSchema, ListNodeDataWithSchema {
/**
* Policy on how child nodes should be treated when an attempt is made to add them multiple times.
*/
@Beta
public enum ChildReusePolicy {
/**
* Do not consider any existing nodes at all, just perform a straight append. Multiple occurrences of a child
* will result in multiple children being emitted. This is almost certainly the wrong policy unless the caller
* prevents such a situation from arising via some different mechanism.
*/
NOOP,
/**
* Do not allow duplicate definition of a child node. This would typically be used when a child cannot be
* encountered multiple times, but the caller does not make any provision to detect such a conflict. If a child
* node would end up being defined a second time, {@link DuplicateChildNodeRejectedException} is reported.
*/
REJECT {
@Override
AbstractNodeDataWithSchema> appendChild(final Collection> view,
final AbstractNodeDataWithSchema> newChild) {
final DataSchemaNode childSchema = newChild.getSchema();
final AbstractNodeDataWithSchema> existing = findExistingChild(view, childSchema);
if (existing != null) {
throw new DuplicateChildNodeRejectedException("Duplicate child " + childSchema.getQName());
}
return super.appendChild(view, newChild);
}
},
/**
* Reuse previously-defined child node. This is most appropriate when a child may be visited multiple times
* and the intent is to append content of each visit. A typical usage is list elements with RFC7950 XML
* encoding, where there is no encapsulating element and hence list entries may be interleaved with other
* children.
*/
REUSE {
@Override
AbstractNodeDataWithSchema> appendChild(final Collection> view,
final AbstractNodeDataWithSchema> newChild) {
final AbstractNodeDataWithSchema> existing = findExistingChild(view, newChild.getSchema());
return existing != null ? existing : super.appendChild(view, newChild);
}
};
AbstractNodeDataWithSchema> appendChild(final Collection> view,
final AbstractNodeDataWithSchema> newChild) {
view.add(newChild);
return newChild;
}
static @Nullable AbstractNodeDataWithSchema> findExistingChild(
final Collection> view, final DataSchemaNode childSchema) {
for (AbstractNodeDataWithSchema> existing : view) {
if (childSchema.equals(existing.getSchema())) {
return existing;
}
}
return null;
}
}
/**
* remaining data nodes (which aren't added via augment). Every of one them should have the same QName.
*/
private final List> children = new ArrayList<>();
// FIXME: hide this when JSON codec is sane
public CompositeNodeDataWithSchema(final T schema) {
super(schema);
}
public static @NonNull CompositeNodeDataWithSchema> of(final DataSchemaNode schema) {
if (schema instanceof ListSchemaNode list) {
return new ListNodeDataWithSchema(list);
} else if (schema instanceof LeafListSchemaNode leafList) {
return new LeafListNodeDataWithSchema(leafList);
} else if (schema instanceof ContainerLike containerLike) {
return new ContainerNodeDataWithSchema(containerLike);
} else {
return new CompositeNodeDataWithSchema<>(schema);
}
}
void addChild(final AbstractNodeDataWithSchema> newChild) {
children.add(newChild);
}
public final AbstractNodeDataWithSchema> addChild(final Deque schemas,
final ChildReusePolicy policy) {
checkArgument(!schemas.isEmpty(), "Expecting at least one schema");
// Pop the first node...
final DataSchemaNode schema = schemas.pop();
if (schemas.isEmpty()) {
// Simple, direct node
return addChild(schema, policy);
}
// The choice/case mess, reuse what we already popped
final DataSchemaNode choiceCandidate = schema;
checkArgument(choiceCandidate instanceof ChoiceSchemaNode, "Expected node of type ChoiceNode but was %s",
choiceCandidate.getClass());
final ChoiceSchemaNode choiceNode = (ChoiceSchemaNode) choiceCandidate;
final DataSchemaNode caseCandidate = schemas.pop();
checkArgument(caseCandidate instanceof CaseSchemaNode, "Expected node of type ChoiceCaseNode but was %s",
caseCandidate.getClass());
final CaseSchemaNode caseNode = (CaseSchemaNode) caseCandidate;
CompositeNodeDataWithSchema> caseNodeDataWithSchema = findChoice(children, choiceCandidate, caseCandidate);
if (caseNodeDataWithSchema == null) {
ChoiceNodeDataWithSchema choiceNodeDataWithSchema = new ChoiceNodeDataWithSchema(choiceNode);
children.add(choiceNodeDataWithSchema);
caseNodeDataWithSchema = choiceNodeDataWithSchema.addCompositeChild(caseNode, ChildReusePolicy.NOOP);
}
return caseNodeDataWithSchema.addChild(schemas, policy);
}
private AbstractNodeDataWithSchema> addChild(final DataSchemaNode schema, final ChildReusePolicy policy) {
AbstractNodeDataWithSchema> newChild = addSimpleChild(schema, policy);
return newChild == null ? addCompositeChild(schema, policy) : newChild;
}
private AbstractNodeDataWithSchema> addSimpleChild(final DataSchemaNode schema, final ChildReusePolicy policy) {
final SimpleNodeDataWithSchema> newChild;
if (schema instanceof LeafSchemaNode leaf) {
newChild = new LeafNodeDataWithSchema(leaf);
} else if (schema instanceof AnyxmlSchemaNode anyxml) {
newChild = new AnyXmlNodeDataWithSchema(anyxml);
} else if (schema instanceof AnydataSchemaNode anydata) {
newChild = new AnydataNodeDataWithSchema(anydata);
} else {
return null;
}
// FIXME: 7.0.0: use policy to determine if we should reuse or replace the child
addChild(newChild);
return newChild;
}
private static CaseNodeDataWithSchema findChoice(final Collection> childNodes,
final DataSchemaNode choiceCandidate, final DataSchemaNode caseCandidate) {
if (childNodes != null) {
for (AbstractNodeDataWithSchema> nodeDataWithSchema : childNodes) {
if (nodeDataWithSchema instanceof ChoiceNodeDataWithSchema childChoice
&& nodeDataWithSchema.getSchema().getQName().equals(choiceCandidate.getQName())) {
CaseNodeDataWithSchema casePrevious = childChoice.getCase();
checkArgument(casePrevious.getSchema().getQName().equals(caseCandidate.getQName()),
"Data from case %s are specified but other data from case %s were specified earlier."
+ " Data aren't from the same case.", caseCandidate.getQName(),
casePrevious.getSchema().getQName());
return casePrevious;
}
}
}
return null;
}
AbstractNodeDataWithSchema> addCompositeChild(final DataSchemaNode schema, final ChildReusePolicy policy) {
return addCompositeChild(of(schema), policy);
}
final AbstractNodeDataWithSchema> addCompositeChild(final CompositeNodeDataWithSchema> newChild,
final ChildReusePolicy policy) {
return policy.appendChild(children, newChild);
}
/**
* Return a hint about how may children we are going to generate.
* @return Size of currently-present node list.
*/
protected final int childSizeHint() {
return children.size();
}
@Override
public void write(final NormalizedNodeStreamWriter writer, final MetadataExtension metaWriter) throws IOException {
// FIXME: we probably want to emit children with the same namespace first
for (var child : children) {
child.write(writer, metaWriter);
}
}
}