BUG-8043: correct LengthConstraint definition
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / util / type / LengthRestrictedTypeBuilder.java
index fbbf77f700540a5ca2b3040d9862831f48db1bcf..7789f5d1d0b34c6117e450a37bd0128e3ea1098d 100644 (file)
  */
 package org.opendaylight.yangtools.yang.model.util.type;
 
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableList.Builder;
-import java.util.Collection;
+import com.google.common.base.Verify;
+import com.google.common.collect.ImmutableRangeSet;
+import com.google.common.collect.ImmutableRangeSet.Builder;
+import com.google.common.collect.Range;
+import com.google.common.collect.RangeSet;
 import java.util.List;
 import java.util.Optional;
-import java.util.function.Function;
-import javax.annotation.Nonnull;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.model.api.ConstraintMetaDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+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.LengthConstraint;
 import org.opendaylight.yangtools.yang.model.api.type.LengthRestrictedTypeDefinition;
-import org.opendaylight.yangtools.yang.model.util.BaseConstraints;
-import org.opendaylight.yangtools.yang.model.util.UnresolvedNumber;
 
 public abstract class LengthRestrictedTypeBuilder<T extends LengthRestrictedTypeDefinition<T>>
         extends AbstractRestrictedTypeBuilder<T> {
-    private List<LengthConstraint> lengthAlternatives;
+    private LengthConstraint lengthConstraint;
 
     LengthRestrictedTypeBuilder(final T baseType, final SchemaPath path) {
-        super(Preconditions.checkNotNull(baseType), path);
+        super(requireNonNull(baseType), path);
     }
 
-    public final void setLengthAlternatives(@Nonnull final Collection<LengthConstraint> lengthAlternatives) {
-        Preconditions.checkState(this.lengthAlternatives == null, "Range alternatives already defined as %s",
-                lengthAlternatives);
-        this.lengthAlternatives = ImmutableList.copyOf(lengthAlternatives);
-        touch();
-    }
-
-    private static List<LengthConstraint> ensureResolvedLengths(final List<LengthConstraint> unresolved,
-            final List<LengthConstraint> baseRangeConstraints) {
-        // First check if we need to resolve anything at all
-        for (LengthConstraint c : unresolved) {
-            if (c.getMax() instanceof UnresolvedNumber || c.getMin() instanceof UnresolvedNumber) {
-                return resolveLengths(unresolved, baseRangeConstraints);
-            }
+    /**
+     * Set a new length constraint.
+     *
+     * @param constraint Constraint metadata
+     * @param ranges Allowed ranges
+     * @throws IllegalStateException if the constraint has already been set
+     * @throws InvalidLengthConstraintException if one of the proposed ranges does not overlap with supertype
+     * @throws NullPointerException if any of the arguments is null
+     */
+    public final void setLengthConstraint(final @NonNull ConstraintMetaDefinition constraint,
+            final @NonNull List<ValueRange> ranges) throws InvalidLengthConstraintException {
+        Preconditions.checkState(lengthConstraint == null, "Length alternatives already defined as %s",
+                lengthConstraint);
+        final LengthConstraint baseLengths = findLenghts();
+        if (ranges.isEmpty()) {
+            lengthConstraint = baseLengths;
+            return;
         }
 
-        // No need, just return the same list
-        return unresolved;
-    }
-
-    private static List<LengthConstraint> resolveLengths(final List<LengthConstraint> unresolved,
-            final List<LengthConstraint> baseLengthConstraints) {
-        final Builder<LengthConstraint> builder = ImmutableList.builder();
-
-        for (LengthConstraint c : unresolved) {
-            final Number max = c.getMax();
-            final Number min = c.getMin();
-
-            if (max instanceof UnresolvedNumber || min instanceof UnresolvedNumber) {
-                final Number rMax = max instanceof UnresolvedNumber
-                    ? ((UnresolvedNumber)max).resolveLength(baseLengthConstraints) : max;
-                final Number rMin = min instanceof UnresolvedNumber
-                    ? ((UnresolvedNumber)min).resolveLength(baseLengthConstraints) : min;
-
-                builder.add(BaseConstraints.newLengthConstraint(rMin, rMax, Optional.ofNullable(c.getDescription()),
-                    Optional.ofNullable(c.getReference()), c.getErrorAppTag(), c.getErrorMessage()));
-            } else {
-                builder.add(c);
-            }
-        }
-
-        return builder.build();
-    }
+        // Run through alternatives and resolve them against the base type
+        requireNonNull(constraint);
+        final Builder<Integer> builder = ImmutableRangeSet.builder();
+        final Range<Integer> span = baseLengths.getAllowedRanges().span();
 
-    private static List<LengthConstraint> ensureTypedLengths(final List<LengthConstraint> lengths,
-            final Class<? extends Number> clazz) {
-        for (LengthConstraint c : lengths) {
-            if (!clazz.isInstance(c.getMin()) || !clazz.isInstance(c.getMax())) {
-                return typedLengths(lengths, clazz);
-            }
+        for (ValueRange c : ranges) {
+            builder.add(Range.closed(resolveLength(c.lowerBound(), span), resolveLength(c.upperBound(), span)));
         }
 
-        return lengths;
-    }
 
-    private static List<LengthConstraint> typedLengths(final List<LengthConstraint> lengths,
-            final Class<? extends Number> clazz) {
-        final Function<Number, Number> function = NumberUtil.converterTo(clazz);
-        Preconditions.checkArgument(function != null, "Unsupported range class %s", clazz);
-
-        final Builder<LengthConstraint> builder = ImmutableList.builder();
-
-        for (LengthConstraint c : lengths) {
-            if (!clazz.isInstance(c.getMin()) || !clazz.isInstance(c.getMax())) {
-                final Number min;
-                final Number max;
-
-                try {
-                    min = function.apply(c.getMin());
-                    max = function.apply(c.getMax());
-                } catch (NumberFormatException e) {
-                    throw new IllegalArgumentException(String.format("Constraint %s does not fit into range of %s",
-                        c, clazz.getSimpleName()), e);
-                }
-                builder.add(BaseConstraints.newLengthConstraint(min, max, Optional.ofNullable(c.getDescription()),
-                    Optional.ofNullable(c.getReference()), c.getErrorAppTag(), c.getErrorMessage()));
-            } else {
-                builder.add(c);
+        // Now verify if new ranges are strict subset of base ranges
+        final RangeSet<Integer> allowed = builder.build();
+        final RangeSet<Integer> baseRanges = baseLengths.getAllowedRanges();
+        for (Range<Integer> range : allowed.asRanges()) {
+            if (!baseRanges.encloses(range)) {
+                throw new InvalidLengthConstraintException("Range %s is not a subset of parent constraint %s", range,
+                    baseRanges);
             }
         }
 
-        return builder.build();
+        lengthConstraint = new ResolvedLengthConstraint(constraint, allowed);
+        touch();
     }
 
-    private static boolean lengthCovered(final List<LengthConstraint> where,
-            final LengthConstraint what) {
-        for (LengthConstraint c : where) {
-            if (NumberUtil.isRangeCovered(what.getMin(), what.getMax(), c.getMin(), c.getMax())) {
-                return true;
-            }
-        }
-
-        return false;
-    }
+    abstract T buildType(LengthConstraint lengthConstraint);
 
     @Override
     final T buildType() {
-        final List<LengthConstraint> baseLengths = findLenghts();
-
-        if (lengthAlternatives == null || lengthAlternatives.isEmpty()) {
-            return buildType(baseLengths);
-        }
-
-        // Run through alternatives and resolve them against the base type
-        final List<LengthConstraint> resolvedLengths = ensureResolvedLengths(lengthAlternatives, baseLengths);
+        return buildType(lengthConstraint != null ? lengthConstraint : findLenghts());
+    }
 
-        // Next up, ensure the of boundaries match base constraints
-        final Class<? extends Number> clazz = baseLengths.get(0).getMin().getClass();
-        final List<LengthConstraint> typedLengths = ensureTypedLengths(resolvedLengths, clazz);
+    abstract LengthConstraint typeLengthConstraints();
 
-        // Now verify if new ranges are strict subset of base ranges
-        for (LengthConstraint c : typedLengths) {
-            if (!lengthCovered(baseLengths, c)) {
-                throw new InvalidLengthConstraintException(c,
-                        "Length constraint %s is not a subset of parent constraints %s", c, baseLengths);
-            }
+    private static Integer resolveLength(final Number unresolved, final Range<Integer> span) {
+        if (unresolved instanceof Integer) {
+            return (Integer) unresolved;
+        }
+        if (unresolved instanceof UnresolvedNumber) {
+            return ((UnresolvedNumber)unresolved).resolveLength(span);
         }
 
-        return buildType(typedLengths);
+        return Verify.verifyNotNull(NumberUtil.converterTo(Integer.class)).apply(unresolved);
     }
 
-    abstract T buildType(List<LengthConstraint> lengthConstraints);
-
-    abstract List<LengthConstraint> typeLengthConstraints();
-
-    private List<LengthConstraint> findLenghts() {
-        List<LengthConstraint> ret = ImmutableList.of();
+    private LengthConstraint findLenghts() {
+        Optional<LengthConstraint> ret = Optional.empty();
         T wlk = getBaseType();
-        while (wlk != null && ret.isEmpty()) {
-            ret = wlk.getLengthConstraints();
+        while (wlk != null && !ret.isPresent()) {
+            ret = wlk.getLengthConstraint();
             wlk = wlk.getBaseType();
         }
 
-        return ret.isEmpty() ? typeLengthConstraints() : ret;
+        return ret.orElse(typeLengthConstraints());
     }
 }