/* * Copyright (c) 2015 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.parser.spi; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition; import org.opendaylight.yangtools.yang.parser.spi.meta.InvalidSubstatementException; import org.opendaylight.yangtools.yang.parser.spi.meta.MissingSubstatementException; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext; import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName; import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase; public final class SubstatementValidator { /** * @deprecated Deprecated since version 1.1.0. Use {@link Builder#addAny(StatementDefinition)}, * {@link Builder#addAtLeast(StatementDefinition, int)}, * {@link Builder#addMandatory(StatementDefinition)}, or * {@link Builder#addMultiple(StatementDefinition)} instead. */ @Deprecated public final static int MAX = Integer.MAX_VALUE; private final Map<StatementDefinition, Cardinality> cardinalityMap; private final Map<StatementDefinition, Cardinality> mandatoryStatements; private final StatementDefinition currentStatement; private SubstatementValidator(final Builder builder) { this.cardinalityMap = builder.cardinalityMap.build(); this.currentStatement = builder.currentStatement; this.mandatoryStatements = ImmutableMap.copyOf(Maps.filterValues(cardinalityMap, c -> c.getMin() > 0)); } public static Builder builder(final StatementDefinition currentStatement) { return new Builder(currentStatement); } public static class Builder { private static final Cardinality ONE_MAX = new Cardinality(1, Integer.MAX_VALUE); private static final Cardinality ONE_ONE = new Cardinality(1, 1); private static final Cardinality ZERO_MAX = new Cardinality(0, Integer.MAX_VALUE); private static final Cardinality ZERO_ONE = new Cardinality(0, 1); private final ImmutableMap.Builder<StatementDefinition, Cardinality> cardinalityMap = ImmutableMap.builder(); private final StatementDefinition currentStatement; private Builder(final StatementDefinition currentStatement) { this.currentStatement = currentStatement; } private Builder add(final StatementDefinition d, final Cardinality c) { cardinalityMap.put(d, c); return this; } public Builder add(final StatementDefinition d, final int min, final int max) { if (max == Integer.MAX_VALUE) { return addAtLeast(d, min); } else if (min == 0) { return addAtMost(d, max); } else { return add(d, new Cardinality(min, max)); } } // Equivalent to min .. Integer.MAX_VALUE public Builder addAtLeast(final StatementDefinition d, final int min) { switch (min) { case 0: return addAny(d); case 1: return addMultiple(d); default: return add(d, new Cardinality(min, Integer.MAX_VALUE)); } } // Equivalent to 0 .. max public Builder addAtMost(final StatementDefinition d, final int max) { return max == Integer.MAX_VALUE ? addAny(d) : add(d, new Cardinality(0, max)); } // Equivalent to 0 .. Integer.MAX_VALUE public Builder addAny(final StatementDefinition d) { return add(d, ZERO_MAX); } // Equivalent to 1 .. 1 public Builder addMandatory(final StatementDefinition d) { return add(d, ONE_ONE); } // Equivalent to 1 .. MAX public Builder addMultiple(final StatementDefinition d) { return add(d, ONE_MAX); } // Equivalent to 0 .. 1 public Builder addOptional(final StatementDefinition d) { return add(d, ZERO_ONE); } public SubstatementValidator build() { return new SubstatementValidator(this); } } public void validate(final StmtContext<?, ?, ?> ctx) throws InvalidSubstatementException, MissingSubstatementException { final Map<StatementDefinition, Counter> stmtCounts = new HashMap<>(); for (StatementContextBase<?, ?, ?> stmtCtx : Iterables.concat(ctx.declaredSubstatements(), ctx.effectiveSubstatements())) { stmtCounts.computeIfAbsent(stmtCtx.getPublicDefinition(), key -> new Counter()).increment(); } // Mark all mandatory statements as not present. We are using a Map instead of a Set, as it provides us with // explicit value in case of failure (which is not important) and a more efficient instantiation performance // (which is important). final Map<StatementDefinition, Cardinality> missingMandatory = new HashMap<>(mandatoryStatements); // Iterate over all statements for (Entry<StatementDefinition, Counter> entry : stmtCounts.entrySet()) { final StatementDefinition key = entry.getKey(); final Cardinality cardinality = cardinalityMap.get(key); final int value = entry.getValue().getValue(); if (cardinality == null) { if (ctx.getFromNamespace(ExtensionNamespace.class, key.getStatementName()) == null) { throw new InvalidSubstatementException(ctx.getStatementSourceReference(), "%s is not valid for %s. Error in module %s (%s)", key, currentStatement, ctx.getRoot().getStatementArgument(), ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot())); } continue; } if (cardinality.getMin() > 0) { if (cardinality.getMin() > value) { throw new InvalidSubstatementException(ctx.getStatementSourceReference(), "Minimal count of %s for %s is %s, detected %s. Error in module %s (%s)", key, currentStatement, cardinality.getMin(), value, ctx.getRoot().getStatementArgument(), ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot())); } // Encountered a mandatory statement, hence we are not missing it missingMandatory.remove(key); } if (cardinality.getMax() < value) { throw new InvalidSubstatementException(ctx.getStatementSourceReference(), "Maximal count of %s for %s is %s, detected %s. Error in module %s (%s)", key, currentStatement, cardinality.getMax(), value, ctx.getRoot().getStatementArgument(), ctx.getFromNamespace(ModuleCtxToModuleQName.class, ctx.getRoot())); } } // Check if there are any mandatory statements we have missed if (!missingMandatory.isEmpty()) { final Entry<StatementDefinition, Cardinality> e = missingMandatory.entrySet().iterator().next(); final StmtContext<?, ?, ?> root = ctx.getRoot(); throw new MissingSubstatementException(ctx.getStatementSourceReference(), "%s is missing %s. Minimal count is %s. Error in module %s (%s)", currentStatement, e.getKey(), e.getValue().getMin(), root.getStatementArgument(), ctx.getFromNamespace(ModuleCtxToModuleQName.class, root)); } } private static final class Cardinality { private final int min; private final int max; private Cardinality(final int min, final int max) { Preconditions.checkArgument(min >= 0, "Min %s cannot be less than 0!"); Preconditions.checkArgument(min <= max, "Min %s can not be greater than max %s!", min, max); this.min = min; this.max = max; } private int getMax() { return max; } private int getMin() { return min; } } private static final class Counter { private int value; void increment() { value++; } int getValue() { return value; } } }