/* * Copyright (c) 2013 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.transform.base.parser; import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedListMultimap; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.AttributesBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeBuilder; import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema; /** * Abstract(base) Parser for DataContainerNodes e.g. ContainerNode, AugmentationNode. */ public abstract class BaseDispatcherParser<E, P extends YangInstanceIdentifier.PathArgument, N extends DataContainerNode<P>, S> implements ExtensibleParser<P, E, N, S> { private final BuildingStrategy<P, N> buildingStrategy; public BaseDispatcherParser(final BuildingStrategy<P, N> buildingStrategy) { this.buildingStrategy = buildingStrategy; } public BaseDispatcherParser() { this.buildingStrategy = new SimpleBuildingStrategy<>(); } /** * * @param schema schema belonging to the type N of NormalizedNode * @return New(empty) instance of a builder to build node identified by schema. */ protected abstract DataContainerNodeBuilder<P, N> getBuilder(S schema); /** * * @param schema schema belonging to the type N of NormalizedNode * @param childQName QName of a child being parsed, QName does not continue revision date * @return schema object for child identified by parent schema: schema and QName childQName */ protected abstract DataSchemaNode getSchemaForChild(S schema, QName childQName); /** * * @param elements elements to be parsed into NormalizedNode * @return map from QName to child elements. Multiple elements are allowed under QName. */ protected abstract LinkedListMultimap<QName, E> mapChildElements(Iterable<E> elements); /** * * @param schema schema belonging to the type N of NormalizedNode * @return map from QName to ChoiceNode schema of child nodes that are * contained within a choice statement under current schema. */ protected abstract Map<QName, ChoiceSchemaNode> mapChildElementsFromChoices(S schema); /** * * @param schema schema belonging to the type N of NormalizedNode * @return map from QName to child elements that are added by augmentation * that targets current schema. */ protected abstract Map<QName, AugmentationSchema> mapChildElementsFromAugments(S schema); /** * * @param schema schema belonging to the type N of NormalizedNode * @param augmentSchema augmentSchema * @return Set of real schema objects that represent child nodes of an * augmentation. Augmentation schema child nodes, if further * augmented, do not contain further augmented, that are crucial for * parsing. The real schema object can be retrieved from parent schema: schema. */ protected abstract Set<DataSchemaNode> getRealSchemasForAugment(S schema, AugmentationSchema augmentSchema); /** * * @return dispatcher object to dispatch parsing of child elements, might be * the same instance if provided parsers are immutable. */ protected abstract NodeParserDispatcher<E> getDispatcher(); /** * can return null only if you override ParsingStrategy and explicitely return null * @param elements elements to be parsed into NormalizedNode * @param schema schema belonging to the type N of NormalizedNode * @return child of DataContainerNode as a result of parsing list of E elements with schema S */ @Nullable @Override public N parse(final Iterable<E> elements, final S schema) { checkAtLeastOneNode(schema, elements); DataContainerNodeBuilder<P, N> containerBuilder = getBuilder(schema); // Map child nodes to QName LinkedListMultimap<QName, E> mappedChildElements = mapChildElements(elements); // Map child nodes from Augments Map<QName, AugmentationSchema> mappedAugmentChildNodes = mapChildElementsFromAugments(schema); LinkedListMultimap<AugmentationSchema, E> augmentsToElements = LinkedListMultimap.create(); // Map child nodes from choices Map<QName, ChoiceSchemaNode> mappedChoiceChildNodes = mapChildElementsFromChoices(schema); LinkedListMultimap<ChoiceSchemaNode, E> choicesToElements = LinkedListMultimap.create(); Map<QName, String> attributes = getAttributes(elements.iterator().next()); if (containerBuilder instanceof AttributesBuilder) { final int size = Iterables.size(elements); Preconditions.checkArgument(size == 1, "Unexpected number of elements: %s, should be 1 for: %s", size, schema); ((AttributesBuilder<?>) containerBuilder).withAttributes(attributes); } //parse keys first if (schema instanceof ListSchemaNode) { for (QName qname : ((ListSchemaNode) schema).getKeyDefinition()) { final QName noRev = qname.withoutRevision(); if (mappedChildElements.get(noRev).isEmpty()) { continue; } DataSchemaNode childSchema = getSchemaForChild(schema, qname); List<E> childrenForQName = mappedChildElements.removeAll(noRev); DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> optionalChildNode = getDispatcher() .dispatchChildElement(childSchema, childrenForQName); if (optionalChildNode != null) { containerBuilder.withChild(optionalChildNode); } } } //stage attribues for strategy before going deeper in the recursion buildingStrategy.prepareAttributes(attributes, containerBuilder); // process Child nodes for (QName childPartialQName : mappedChildElements.keySet()) { DataSchemaNode childSchema = getSchemaForChild(schema, childPartialQName); //with strict parsing an exception would be already thrown, with nonstrict we want to ignore this node if (childSchema == null) { continue; } List<E> childrenForQName = mappedChildElements.get(childPartialQName); // Augment if (isMarkedAs(mappedAugmentChildNodes, childSchema.getQName())) { AugmentationSchema augmentationSchema = mappedAugmentChildNodes.get(childSchema.getQName()); augmentsToElements.putAll(augmentationSchema, childrenForQName); // Choices } else if (isMarkedAs(mappedChoiceChildNodes, childSchema.getQName())) { ChoiceSchemaNode choiceSchema = mappedChoiceChildNodes.get(childSchema.getQName()); choicesToElements.putAll(choiceSchema, childrenForQName); // Regular child nodes } else { DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> optionalChildNode = getDispatcher() .dispatchChildElement(childSchema, childrenForQName); if (optionalChildNode != null) { containerBuilder.withChild(optionalChildNode); } } } // TODO ordering is not preserved for choice and augment elements for (ChoiceSchemaNode choiceSchema : choicesToElements.keySet()) { DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> optionalChild = getDispatcher() .dispatchChildElement(choiceSchema, choicesToElements.get(choiceSchema)); if (optionalChild != null) { containerBuilder.withChild(optionalChild); } } for (AugmentationSchema augmentSchema : augmentsToElements.keySet()) { Set<DataSchemaNode> realChildSchemas = getRealSchemasForAugment(schema, augmentSchema); EffectiveAugmentationSchema augSchemaProxy = new EffectiveAugmentationSchema(augmentSchema, realChildSchemas); DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> optionalChild = getDispatcher() .dispatchChildElement(augSchemaProxy, augmentsToElements.get(augmentSchema)); if (optionalChild != null) { containerBuilder.withChild(optionalChild); } } return buildingStrategy.build(containerBuilder); } @Override public BuildingStrategy<P, N> getBuildingStrategy() { return buildingStrategy; } protected Map<QName, String> getAttributes(final E e) { return Collections.emptyMap(); } protected boolean strictParsing() { return true; } private static boolean isMarkedAs(final Map<QName, ?> mappedAugmentChildNodes, final QName qName) { return mappedAugmentChildNodes.containsKey(qName); } protected void checkOnlyOneNode(final S schema, final Iterable<E> childNodes) { final int size = Iterables.size(childNodes); Preconditions.checkArgument(size == 1, "Node detected multiple times, should be 1, identified by: %s, found: %s", schema, childNodes); } private void checkAtLeastOneNode(final S schema, final Iterable<E> childNodes) { Preconditions.checkArgument(!Iterables.isEmpty(childNodes), "Node detected 0 times, should be at least 1, identified by: %s, found: %s", schema, childNodes); } public static class SimpleBuildingStrategy<P extends YangInstanceIdentifier.PathArgument, N extends DataContainerNode<P>> implements BuildingStrategy<P, N> { @Override public N build(final NormalizedNodeBuilder<P, ?, N> builder) { return builder.build(); } @Override public void prepareAttributes(final Map<QName, String> attributes, final NormalizedNodeBuilder<P, ?, N> containerBuilder) { // NOOP } } }