/*
* 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.stmt.rfc6020;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.YangVersion;
import org.opendaylight.yangtools.yang.model.api.SchemaPath;
import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.TypeEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
import org.opendaylight.yangtools.yang.model.util.UnresolvedNumber;
import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
import org.opendaylight.yangtools.yang.parser.spi.meta.QNameCacheNamespace;
import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.type.LengthConstraintEffectiveImpl;
import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.type.RangeConstraintEffectiveImpl;
/**
* util class for manipulating YANG base and extended types implementation
*/
public final class TypeUtils {
public static final String BINARY = "binary";
public static final String BITS = "bits";
public static final String BOOLEAN = "boolean";
public static final String DECIMAL64 = "decimal64";
public static final String EMPTY = "empty";
public static final String ENUMERATION = "enumeration";
public static final String IDENTITY_REF = "identityref";
public static final String INSTANCE_IDENTIFIER = "instance-identifier";
public static final String INT8 = "int8";
public static final String INT16 = "int16";
public static final String INT32 = "int32";
public static final String INT64 = "int64";
public static final String LEAF_REF = "leafref";
public static final String STRING = "string";
public static final String UINT8 = "uint8";
public static final String UINT16 = "uint16";
public static final String UINT32 = "uint32";
public static final String UINT64 = "uint64";
public static final String UNION = "union";
private static final Set<String> BUILT_IN_TYPES =
ImmutableSet.of(BINARY, BITS, BOOLEAN, DECIMAL64, EMPTY, ENUMERATION, IDENTITY_REF, INSTANCE_IDENTIFIER,
INT8, INT16, INT32, INT64, LEAF_REF, STRING, UINT8, UINT16, UINT32, UINT64, UNION);
private static final Set<String> TYPE_BODY_STMTS = ImmutableSet.of(
DECIMAL64, ENUMERATION, LEAF_REF, IDENTITY_REF, BITS, UNION);
private static final Splitter PIPE_SPLITTER = Splitter.on('|').trimResults();
private static final Splitter TWO_DOTS_SPLITTER = Splitter.on("..").trimResults();
private TypeUtils() {
}
private static BigDecimal yangConstraintToBigDecimal(final Number number) {
if (UnresolvedNumber.max().equals(number)) {
return RangeStatementImpl.YANG_MAX_NUM;
}
if (UnresolvedNumber.min().equals(number)) {
return RangeStatementImpl.YANG_MIN_NUM;
}
return new BigDecimal(number.toString());
}
private static int compareNumbers(final Number n1, final Number n2) {
final BigDecimal num1 = yangConstraintToBigDecimal(n1);
final BigDecimal num2 = yangConstraintToBigDecimal(n2);
return new BigDecimal(num1.toString()).compareTo(new BigDecimal(num2.toString()));
}
private static Number parseIntegerConstraintValue(final StmtContext<?, ?, ?> ctx, final String value) {
if ("max".equals(value)) {
return UnresolvedNumber.max();
}
if ("min".equals(value)) {
return UnresolvedNumber.min();
}
try {
return new BigInteger(value);
} catch (final NumberFormatException e) {
throw new SourceException(String.format("Value %s is not a valid integer", value),
ctx.getStatementSourceReference(), e);
}
}
private static Number parseDecimalConstraintValue(final StmtContext<?, ?, ?> ctx, final String value) {
if ("max".equals(value)) {
return UnresolvedNumber.max();
}
if ("min".equals(value)) {
return UnresolvedNumber.min();
}
try {
return value.indexOf('.') != -1 ? new BigDecimal(value) : new BigInteger(value);
} catch (final NumberFormatException e) {
throw new SourceException(String.format("Value %s is not a valid decimal number", value),
ctx.getStatementSourceReference(), e);
}
}
public static List<RangeConstraint> parseRangeListFromString(final StmtContext<?, ?, ?> ctx,
final String rangeArgument) {
final Optional<String> description = Optional.absent();
final Optional<String> reference = Optional.absent();
final List<RangeConstraint> rangeConstraints = new ArrayList<>();
for (final String singleRange : PIPE_SPLITTER.split(rangeArgument)) {
final Iterator<String> boundaries = TWO_DOTS_SPLITTER.splitToList(singleRange).iterator();
final Number min = parseDecimalConstraintValue(ctx, boundaries.next());
final Number max;
if (boundaries.hasNext()) {
max = parseDecimalConstraintValue(ctx, boundaries.next());
// if min larger than max then error
InferenceException.throwIf(compareNumbers(min, max) == 1, ctx.getStatementSourceReference(),
"Range constraint %s has descending order of boundaries; should be ascending", singleRange);
SourceException.throwIf(boundaries.hasNext(), ctx.getStatementSourceReference(),
"Wrong number of boundaries in range constraint %s", singleRange);
} else {
max = min;
}
// some of intervals overlapping
if (rangeConstraints.size() > 1 && compareNumbers(min, Iterables.getLast(rangeConstraints).getMax()) != 1) {
throw new InferenceException(ctx.getStatementSourceReference(),
"Some of the ranges in %s are not disjoint", rangeArgument);
}
rangeConstraints.add(new RangeConstraintEffectiveImpl(min, max, description, reference));
}
return rangeConstraints;
}
public static List<LengthConstraint> parseLengthListFromString(final StmtContext<?, ?, ?> ctx,
final String lengthArgument) {
final Optional<String> description = Optional.absent();
final Optional<String> reference = Optional.absent();
final List<LengthConstraint> lengthConstraints = new ArrayList<>();
for (final String singleRange : PIPE_SPLITTER.split(lengthArgument)) {
final Iterator<String> boundaries = TWO_DOTS_SPLITTER.splitToList(singleRange).iterator();
final Number min = parseIntegerConstraintValue(ctx, boundaries.next());
final Number max;
if (boundaries.hasNext()) {
max = parseIntegerConstraintValue(ctx, boundaries.next());
// if min larger than max then error
Preconditions.checkArgument(compareNumbers(min, max) != 1,
"Length constraint %s has descending order of boundaries; should be ascending. Statement source at %s",
singleRange, ctx.getStatementSourceReference());
Preconditions.checkArgument(!boundaries.hasNext(),
"Wrong number of boundaries in length constraint %s. Statement source at %s", singleRange,
ctx.getStatementSourceReference());
} else {
max = min;
}
// some of intervals overlapping
if (lengthConstraints.size() > 1 && compareNumbers(min, Iterables.getLast(lengthConstraints).getMax()) != 1) {
throw new InferenceException(ctx.getStatementSourceReference(),
"Some of the length ranges in %s are not disjoint", lengthArgument);
}
lengthConstraints.add(new LengthConstraintEffectiveImpl(min, max, description, reference));
}
return lengthConstraints;
}
public static boolean isYangTypeBodyStmtString(final String typeName) {
return TYPE_BODY_STMTS.contains(typeName);
}
public static boolean isYangBuiltInTypeString(final String typeName) {
return BUILT_IN_TYPES.contains(typeName);
}
public static SchemaPath typeEffectiveSchemaPath(final StmtContext<?, ?, ?> stmtCtx) {
final SchemaPath path = stmtCtx.getSchemaPath().get();
final SchemaPath parent = path.getParent();
final QName parentQName = parent.getLastComponent();
Preconditions.checkArgument(parentQName != null, "Path %s has an empty parent", path);
final QName qname = stmtCtx.getFromNamespace(QNameCacheNamespace.class,
QName.create(parentQName, path.getLastComponent().getLocalName()));
return parent.createChild(qname);
}
/**
* Checks whether supplied type has any of specified default values marked
* with an if-feature. This method creates mutable copy of supplied set of
* default values.
*
* @param yangVersion
* yang version
* @param typeStmt
* type statement which should be checked
* @param defaultValues
* set of default values which should be checked. The method
* creates mutable copy of this set
*
* @return true if any of specified default values is marked with an
* if-feature, otherwise false
*
* @throws IllegalStateException
* if any of specified default values has not been found
*/
public static boolean hasDefaultValueMarkedWithIfFeature(final YangVersion yangVersion,
final TypeEffectiveStatement<?> typeStmt, final Set<String> defaultValues) {
return !defaultValues.isEmpty() && yangVersion == YangVersion.VERSION_1_1
&& isRelevantForIfFeatureCheck(typeStmt)
&& isAnyDefaultValueMarkedWithIfFeature(typeStmt, new HashSet<>(defaultValues));
}
/**
* Checks whether supplied type has specified default value marked with an
* if-feature. This method creates mutable set of supplied default value.
*
* @param yangVersion
* yang version
* @param typeStmt
* type statement which should be checked
* @param defaultValue
* default value to be checked
*
* @return true if specified default value is marked with an if-feature,
* otherwise false
*
* @throws IllegalStateException
* if specified default value has not been found
*/
public static boolean hasDefaultValueMarkedWithIfFeature(final YangVersion yangVersion,
final TypeEffectiveStatement<?> typeStmt, final String defaultValue) {
final HashSet<String> defaultValues = new HashSet<>();
defaultValues.add(defaultValue);
return !Strings.isNullOrEmpty(defaultValue) && yangVersion == YangVersion.VERSION_1_1
&& isRelevantForIfFeatureCheck(typeStmt)
&& isAnyDefaultValueMarkedWithIfFeature(typeStmt, defaultValues);
}
private static boolean isRelevantForIfFeatureCheck(final TypeEffectiveStatement<?> typeStmt) {
final TypeDefinition<?> typeDefinition = typeStmt.getTypeDefinition();
return typeDefinition instanceof EnumTypeDefinition || typeDefinition instanceof BitsTypeDefinition
|| typeDefinition instanceof UnionTypeDefinition;
}
private static boolean isAnyDefaultValueMarkedWithIfFeature(final TypeEffectiveStatement<?> typeStmt,
final Set<String> defaultValues) {
final Collection<? extends EffectiveStatement<?, ?>> effectiveSubstatements = typeStmt.effectiveSubstatements();
for (final EffectiveStatement<?, ?> effectiveSubstatement : effectiveSubstatements) {
if (YangStmtMapping.BIT.equals(effectiveSubstatement.statementDefinition())) {
final QName bitQName = (QName) effectiveSubstatement.argument();
if (defaultValues.remove(bitQName.getLocalName()) && containsIfFeature(effectiveSubstatement)) {
return true;
}
} else if (YangStmtMapping.ENUM.equals(effectiveSubstatement.statementDefinition())
&& defaultValues.remove(effectiveSubstatement.argument())
&& containsIfFeature(effectiveSubstatement)) {
return true;
} else if (effectiveSubstatement instanceof TypeEffectiveStatement) {
return isAnyDefaultValueMarkedWithIfFeature((TypeEffectiveStatement<?>) effectiveSubstatement,
defaultValues);
}
}
Preconditions.checkState(defaultValues.isEmpty(), "Unable to find following default values %s", defaultValues);
return false;
}
private static boolean containsIfFeature(final EffectiveStatement<?, ?> effectiveStatement) {
for (final EffectiveStatement<?, ?> effectiveSubstatement : effectiveStatement.effectiveSubstatements()) {
if (YangStmtMapping.IF_FEATURE.equals(effectiveSubstatement.statementDefinition())) {
return true;
}
}
return false;
}
}