BUG-8043: correct RangeConstraint definition
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / rfc6020 / TypeUtils.java
index 9b9683d41c34d0cd828112daea6bc7b647c75b62..8f832ca79cfde8e868446209f31207d81f8f4bfb 100644 (file)
@@ -7,29 +7,39 @@
  */
 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.ImmutableMap;
 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.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 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.type.LengthConstraint;
-import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
-import org.opendaylight.yangtools.yang.model.util.UnresolvedNumber;
+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.stmt.UnresolvedNumber;
+import org.opendaylight.yangtools.yang.model.api.stmt.ValueRange;
+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.UnionTypeDefinition;
+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.stmt.rfc6020.effective.type.LengthConstraintEffectiveImpl;
-import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.type.RangeConstraintEffectiveImpl;
+import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 
 /**
-* util class for manipulating YANG base and extended types implementation
+* Utility class for manipulating YANG base and extended types implementation.
 */
 public final class TypeUtils {
 
@@ -53,9 +63,27 @@ public final class TypeUtils {
     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 Map<String, String> BUILT_IN_TYPES = ImmutableMap.<String, String>builder()
+        .put(BINARY, BINARY)
+        .put(BITS, BITS)
+        .put(BOOLEAN, BOOLEAN)
+        .put(DECIMAL64, DECIMAL64)
+        .put(EMPTY, EMPTY)
+        .put(ENUMERATION, ENUMERATION)
+        .put(IDENTITY_REF,IDENTITY_REF)
+        .put(INSTANCE_IDENTIFIER, INSTANCE_IDENTIFIER)
+        .put(INT8, INT8)
+        .put(INT16, INT16)
+        .put(INT32, INT32)
+        .put(INT64, INT64)
+        .put(LEAF_REF, LEAF_REF)
+        .put(STRING, STRING)
+        .put(UINT8, UINT8)
+        .put(UINT16, UINT16)
+        .put(UINT32, UINT32)
+        .put(UINT64, UINT64)
+        .put(UNION, UNION)
+        .build();
 
     private static final Set<String> TYPE_BODY_STMTS = ImmutableSet.of(
         DECIMAL64, ENUMERATION, LEAF_REF, IDENTITY_REF, BITS, UNION);
@@ -84,7 +112,7 @@ public final class TypeUtils {
         return new BigDecimal(num1.toString()).compareTo(new BigDecimal(num2.toString()));
     }
 
-    private static Number parseIntegerConstraintValue(final String value) {
+    private static Number parseIntegerConstraintValue(final StmtContext<?, ?, ?> ctx, final String value) {
         if ("max".equals(value)) {
             return UnresolvedNumber.max();
         }
@@ -94,12 +122,12 @@ public final class TypeUtils {
 
         try {
             return new BigInteger(value);
-        } catch (NumberFormatException e) {
-            throw new IllegalArgumentException(String.format("Value %s is not a valid integer", value), e);
+        } catch (final NumberFormatException e) {
+            throw new SourceException(ctx.getStatementSourceReference(), e, "Value %s is not a valid integer", value);
         }
     }
 
-    private static Number parseDecimalConstraintValue(final String value) {
+    private static Number parseDecimalConstraintValue(final StmtContext<?, ?, ?> ctx, final String value) {
         if ("max".equals(value)) {
             return UnresolvedNumber.max();
         }
@@ -109,84 +137,74 @@ public final class TypeUtils {
 
         try {
             return value.indexOf('.') != -1 ? new BigDecimal(value) : new BigInteger(value);
-        } catch (NumberFormatException e) {
-            throw new IllegalArgumentException(String.format("Value %s is not a valid decimal number", value), e);
+        } 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 String rangeArgument) {
-
-        Optional<String> description = Optional.absent();
-        Optional<String> reference = Optional.absent();
-
-        List<RangeConstraint> rangeConstraints = new ArrayList<>();
+    public static List<ValueRange> parseRangeListFromString(final StmtContext<?, ?, ?> ctx,
+            final String rangeArgument) {
+        final List<ValueRange> ranges = new ArrayList<>();
 
         for (final String singleRange : PIPE_SPLITTER.split(rangeArgument)) {
-            final Iterator<String> boundaries = TWO_DOTS_SPLITTER.splitToList(singleRange).iterator();
-            final Number min = parseDecimalConstraintValue(boundaries.next());
+            final Iterator<String> boundaries = TWO_DOTS_SPLITTER.split(singleRange).iterator();
+            final Number min = parseDecimalConstraintValue(ctx, boundaries.next());
 
             final Number max;
             if (boundaries.hasNext()) {
-                max = parseDecimalConstraintValue(boundaries.next());
+                max = parseDecimalConstraintValue(ctx, boundaries.next());
 
                 // if min larger than max then error
-                if (compareNumbers(min, max) == 1) {
-                    throw new IllegalArgumentException(String.format(
-                            "Range constraint %s has descending order of boundaries; should be ascending", singleRange));
-                }
-                if (boundaries.hasNext()) {
-                    throw new IllegalArgumentException("Wrong number of boundaries in range constraint " + singleRange);
-                }
+                SourceException.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 IllegalArgumentException(String.format("Some of the ranges in %s are not disjoint",
-                        rangeArgument));
-            }
-
-            rangeConstraints.add(new RangeConstraintEffectiveImpl(min, max, description, reference));
+            InferenceException.throwIf(ranges.size() > 1
+                && compareNumbers(min, Iterables.getLast(ranges).upperBound()) != 1,
+                ctx.getStatementSourceReference(),  "Some of the value ranges in %s are not disjoint",
+                rangeArgument);
+            ranges.add(ValueRange.of(min, max));
         }
 
-        return rangeConstraints;
+        return ranges;
     }
 
-    public static List<LengthConstraint> parseLengthListFromString(final String rangeArgument) {
+    public static List<ValueRange> parseLengthListFromString(final StmtContext<?, ?, ?> ctx,
+            final String lengthArgument) {
+        final List<ValueRange> ranges = new ArrayList<>();
 
-        Optional<String> description = Optional.absent();
-        Optional<String> reference = Optional.absent();
-
-        List<LengthConstraint> rangeConstraints = new ArrayList<>();
-
-        for (final String singleRange : PIPE_SPLITTER.split(rangeArgument)) {
-            final Iterator<String> boundaries = TWO_DOTS_SPLITTER.splitToList(singleRange).iterator();
-            final Number min = parseIntegerConstraintValue(boundaries.next());
+        for (final String singleRange : PIPE_SPLITTER.split(lengthArgument)) {
+            final Iterator<String> boundaries = TWO_DOTS_SPLITTER.split(singleRange).iterator();
+            final Number min = parseIntegerConstraintValue(ctx, boundaries.next());
 
             final Number max;
             if (boundaries.hasNext()) {
-                max = parseIntegerConstraintValue(boundaries.next());
+                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", singleRange);
-                Preconditions.checkArgument(!boundaries.hasNext(), "Wrong number of boundaries in length constraint %s",
-                        singleRange);
+                SourceException.throwIf(compareNumbers(min, max) == 1, ctx.getStatementSourceReference(),
+                        "Length constraint %s has descending order of boundaries; should be ascending.", singleRange);
+                SourceException.throwIf(boundaries.hasNext(), ctx.getStatementSourceReference(),
+                        "Wrong number of boundaries in length constraint %s.", singleRange);
             } else {
                 max = min;
             }
 
             // some of intervals overlapping
-            if (rangeConstraints.size() > 1 && compareNumbers(min, Iterables.getLast(rangeConstraints).getMax()) != 1) {
-                throw new IllegalArgumentException(String.format("Some of the length ranges in %s are not disjoint",
-                        rangeArgument));
-            }
-
-            rangeConstraints.add(new LengthConstraintEffectiveImpl(min, max, description, reference));
+            InferenceException.throwIf(ranges.size() > 1
+                && compareNumbers(min, Iterables.getLast(ranges).upperBound()) != 1,
+                        ctx.getStatementSourceReference(),  "Some of the length ranges in %s are not disjoint",
+                        lengthArgument);
+            ranges.add(ValueRange.of(min, max));
         }
 
-        return rangeConstraints;
+        return ranges;
     }
 
     public static boolean isYangTypeBodyStmtString(final String typeName) {
@@ -194,14 +212,106 @@ public final class TypeUtils {
     }
 
     public static boolean isYangBuiltInTypeString(final String typeName) {
-        return BUILT_IN_TYPES.contains(typeName);
+        return BUILT_IN_TYPES.containsKey(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(path.getParent().getLastComponent(), path.getLastComponent().getLocalName()));
-        return path.getParent().createChild(qname);
+            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
+     */
+    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
+     */
+    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);
+    }
+
+    static String findBuiltinString(final String rawArgument) {
+        return BUILT_IN_TYPES.get(rawArgument);
+    }
+
+    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 Iterator<? extends EffectiveStatement<?, ?>> iter = typeStmt.effectiveSubstatements().iterator();
+        while (iter.hasNext() && !defaultValues.isEmpty()) {
+            final EffectiveStatement<?, ?> effectiveSubstatement = iter.next();
+            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 && isAnyDefaultValueMarkedWithIfFeature(
+                    (TypeEffectiveStatement<?>) effectiveSubstatement, defaultValues)) {
+                return true;
+            }
+        }
+
+        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;
     }
 }