+
+ public static void copyFromSourceToTarget(final StatementContextBase<?, ?, ?> sourceCtx,
+ final StatementContextBase<?, ?, ?> targetCtx) {
+ final CopyType typeOfCopy = UsesStatement.class.equals(sourceCtx.getParentContext().getPublicDefinition()
+ .getDeclaredRepresentationClass()) ? CopyType.ADDED_BY_USES_AUGMENTATION
+ : CopyType.ADDED_BY_AUGMENTATION;
+ /*
+ * Since Yang 1.1, if an augmentation is made conditional with a
+ * "when" statement, it is allowed to add mandatory nodes.
+ */
+ final boolean skipCheckOfMandatoryNodes = YangVersion.VERSION_1_1.equals(sourceCtx.getRootVersion())
+ && isConditionalAugmentStmt(sourceCtx);
+
+ final Collection<? extends Mutable<?, ?, ?>> declared = sourceCtx.mutableDeclaredSubstatements();
+ final Collection<? extends Mutable<?, ?, ?>> effective = sourceCtx.mutableEffectiveSubstatements();
+ final Collection<Mutable<?, ?, ?>> buffer = new ArrayList<>(declared.size() + effective.size());
+
+ for (final Mutable<?, ?, ?> originalStmtCtx : declared) {
+ if (originalStmtCtx.isSupportedByFeatures()) {
+ copyStatement(originalStmtCtx, targetCtx, typeOfCopy, buffer, skipCheckOfMandatoryNodes);
+ }
+ }
+ for (final Mutable<?, ?, ?> originalStmtCtx : effective) {
+ copyStatement(originalStmtCtx, targetCtx, typeOfCopy, buffer, skipCheckOfMandatoryNodes);
+ }
+
+ targetCtx.addEffectiveSubstatements(buffer);
+ }
+
+ /**
+ * Checks whether supplied statement context is conditional augment
+ * statement.
+ *
+ * @param ctx
+ * statement context to be checked
+ *
+ * @return true if supplied statement context is conditional augment
+ * statement, otherwise false
+ */
+ private static boolean isConditionalAugmentStmt(final StmtContext<?, ?, ?> ctx) {
+ return ctx.getPublicDefinition() == YangStmtMapping.AUGMENT
+ && StmtContextUtils.findFirstSubstatement(ctx, WhenStatement.class) != null;
+ }
+
+ private static void copyStatement(final Mutable<?, ?, ?> original, final StatementContextBase<?, ?, ?> target,
+ final CopyType typeOfCopy, final Collection<Mutable<?, ?, ?>> buffer,
+ final boolean skipCheckOfMandatoryNodes) {
+ if (needToCopyByAugment(original)) {
+ validateNodeCanBeCopiedByAugment(original, target, typeOfCopy, skipCheckOfMandatoryNodes);
+
+ final Mutable<?, ?, ?> copy = original.createCopy(target, typeOfCopy);
+ buffer.add(copy);
+ } else if (isReusedByAugment(original)) {
+ buffer.add(original);
+ }
+ }
+
+ private static void validateNodeCanBeCopiedByAugment(final StmtContext<?, ?, ?> sourceCtx,
+ final StatementContextBase<?, ?, ?> targetCtx, final CopyType typeOfCopy,
+ final boolean skipCheckOfMandatoryNodes) {
+
+ if (WhenStatement.class.equals(sourceCtx.getPublicDefinition().getDeclaredRepresentationClass())) {
+ return;
+ }
+
+ if (!skipCheckOfMandatoryNodes && typeOfCopy == CopyType.ADDED_BY_AUGMENTATION
+ && reguiredCheckOfMandatoryNodes(sourceCtx, targetCtx)) {
+ checkForMandatoryNodes(sourceCtx);
+ }
+
+ final List<Mutable<?, ?, ?>> targetSubStatements = ImmutableList.<Mutable<?, ?, ?>>builder()
+ .addAll(targetCtx.mutableDeclaredSubstatements()).addAll(targetCtx.mutableEffectiveSubstatements())
+ .build();
+
+ for (final Mutable<?, ?, ?> subStatement : targetSubStatements) {
+ final boolean sourceIsDataNode = DataDefinitionStatement.class.isAssignableFrom(sourceCtx
+ .getPublicDefinition().getDeclaredRepresentationClass());
+ final boolean targetIsDataNode = DataDefinitionStatement.class.isAssignableFrom(subStatement
+ .getPublicDefinition().getDeclaredRepresentationClass());
+ final boolean qNamesEqual = sourceIsDataNode && targetIsDataNode
+ && Objects.equals(sourceCtx.getStatementArgument(), subStatement.getStatementArgument());
+
+ InferenceException.throwIf(qNamesEqual, sourceCtx.getStatementSourceReference(),
+ "An augment cannot add node named '%s' because this name is already used in target",
+ sourceCtx.rawStatementArgument());
+ }
+ }
+
+ private static void checkForMandatoryNodes(final StmtContext<?, ?, ?> sourceCtx) {
+ if (StmtContextUtils.isNonPresenceContainer(sourceCtx)) {
+ /*
+ * We need to iterate over both declared and effective sub-statements,
+ * because a mandatory node can be:
+ * a) declared in augment body
+ * b) added to augment body also via uses of a grouping and
+ * such sub-statements are stored in effective sub-statements collection.
+ */
+ sourceCtx.declaredSubstatements().forEach(Definition::checkForMandatoryNodes);
+ sourceCtx.effectiveSubstatements().forEach(Definition::checkForMandatoryNodes);
+ }
+
+ InferenceException.throwIf(StmtContextUtils.isMandatoryNode(sourceCtx),
+ sourceCtx.getStatementSourceReference(),
+ "An augment cannot add node '%s' because it is mandatory and in module different than target",
+ sourceCtx.rawStatementArgument());
+ }
+
+ private static boolean reguiredCheckOfMandatoryNodes(final StmtContext<?, ?, ?> sourceCtx,
+ Mutable<?, ?, ?> targetCtx) {
+ /*
+ * If the statement argument is not QName, it cannot be mandatory
+ * statement, therefore return false and skip mandatory nodes validation
+ */
+ if (!(sourceCtx.getStatementArgument() instanceof QName)) {
+ return false;
+ }
+ final QName sourceStmtQName = (QName) sourceCtx.getStatementArgument();
+
+ // RootStatementContext, for example
+ final Mutable<?, ?, ?> root = targetCtx.getRoot();
+ do {
+ Verify.verify(targetCtx.getStatementArgument() instanceof QName,
+ "Argument of augment target statement must be QName.");
+ final QName targetStmtQName = (QName) targetCtx.getStatementArgument();
+ /*
+ * If target is from another module, return true and perform
+ * mandatory nodes validation
+ */
+ if (!Utils.belongsToTheSameModule(targetStmtQName, sourceStmtQName)) {
+ return true;
+ }
+
+ /*
+ * If target or one of the target's ancestors from the same namespace
+ * is a presence container
+ * or is non-mandatory choice
+ * or is non-mandatory list
+ * return false and skip mandatory nodes validation, because these nodes
+ * are not mandatory node containers according to RFC 6020 section 3.1.
+ */
+ if (StmtContextUtils.isPresenceContainer(targetCtx)
+ || StmtContextUtils.isNotMandatoryNodeOfType(targetCtx, YangStmtMapping.CHOICE)
+ || StmtContextUtils.isNotMandatoryNodeOfType(targetCtx, YangStmtMapping.LIST)) {
+ return false;
+ }
+ } while ((targetCtx = targetCtx.getParentContext()) != root);
+
+ /*
+ * All target node's parents belong to the same module as source node,
+ * therefore return false and skip mandatory nodes validation.
+ */
+ return false;
+ }
+
+ private static final Set<YangStmtMapping> NOCOPY_DEF_SET = ImmutableSet.of(YangStmtMapping.USES, YangStmtMapping.WHEN,
+ YangStmtMapping.DESCRIPTION, YangStmtMapping.REFERENCE, YangStmtMapping.STATUS);
+
+ public static boolean needToCopyByAugment(final StmtContext<?, ?, ?> stmtContext) {
+ return !NOCOPY_DEF_SET.contains(stmtContext.getPublicDefinition());
+ }
+
+ private static final Set<YangStmtMapping> REUSED_DEF_SET = ImmutableSet.of(YangStmtMapping.TYPEDEF);
+
+ public static boolean isReusedByAugment(final StmtContext<?, ?, ?> stmtContext) {
+ return REUSED_DEF_SET.contains(stmtContext.getPublicDefinition());
+ }
+
+ static boolean isSupportedAugmentTarget(final StmtContext<?, ?, ?> substatementCtx) {
+ /*
+ * :TODO Substatement must be allowed augment target type e.g.
+ * Container, etc... and must not be for example grouping, identity etc.
+ * It is problem in case when more than one substatements have the same
+ * QName, for example Grouping and Container are siblings and they have
+ * the same QName. We must find the Container and the Grouping must be
+ * ignored as disallowed augment target.
+ */
+ final Collection<?> allowedAugmentTargets = substatementCtx.getFromNamespace(
+ ValidationBundlesNamespace.class, ValidationBundleType.SUPPORTED_AUGMENT_TARGETS);
+
+ // if no allowed target is returned we consider all targets allowed
+ return allowedAugmentTargets == null || allowedAugmentTargets.isEmpty()
+ || allowedAugmentTargets.contains(substatementCtx.getPublicDefinition());
+ }
+
+ @Override
+ protected SubstatementValidator getSubstatementValidator() {
+ return SUBSTATEMENT_VALIDATOR;
+ }