BUG-4268: clarify length constraint API contract
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / util / type / LengthRestrictedTypeBuilder.java
index 57cccbfd5989d1c3b5495d6fbab2d60bc46d0704..9c1b84bec477e1c54716b40dcacabc9654003fa8 100644 (file)
@@ -7,16 +7,22 @@
  */
 package org.opendaylight.yangtools.yang.model.util.type;
 
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import java.util.Collection;
 import java.util.List;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
+import org.opendaylight.yangtools.yang.model.util.BaseConstraints;
+import org.opendaylight.yangtools.yang.model.util.UnresolvedNumber;
 
 public abstract class LengthRestrictedTypeBuilder<T extends TypeDefinition<T>> extends AbstractRestrictedTypeBuilder<T> {
-    private Collection<LengthConstraint> lengthAlternatives;
+    private List<LengthConstraint> lengthAlternatives;
 
     LengthRestrictedTypeBuilder(final T baseType, final SchemaPath path) {
         super(Preconditions.checkNotNull(baseType), path);
@@ -25,16 +31,134 @@ public abstract class LengthRestrictedTypeBuilder<T extends TypeDefinition<T>> e
     public final void setLengthAlternatives(@Nonnull final Collection<LengthConstraint> lengthAlternatives) {
         Preconditions.checkState(this.lengthAlternatives == null, "Range alternatives already defined as %s",
                 lengthAlternatives);
-        this.lengthAlternatives = Preconditions.checkNotNull(lengthAlternatives);
+        this.lengthAlternatives = ImmutableList.copyOf(lengthAlternatives);
         touch();
     }
 
-    final List<LengthConstraint> calculateLenghtConstraints(final List<LengthConstraint> baseLengthConstraints) {
+    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);
+            }
+        }
+
+        // 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.fromNullable(c.getDescription()),
+                    Optional.fromNullable(c.getReference())));
+            } else {
+                builder.add(c);
+            }
+        }
+
+        return builder.build();
+    }
+
+    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);
+            }
+        }
+
+        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, 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.fromNullable(c.getDescription()),
+                    Optional.fromNullable(c.getReference())));
+            } else {
+                builder.add(c);
+            }
+        }
+
+        return builder.build();
+    }
+
+    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(List<LengthConstraint> lengthConstraints);
+    abstract List<LengthConstraint> getLengthConstraints(T type);
+    abstract List<LengthConstraint> typeLengthConstraints();
+
+    private List<LengthConstraint> findLenghts() {
+        List<LengthConstraint> ret = ImmutableList.of();
+        T wlk = getBaseType();
+        while (wlk != null && ret.isEmpty()) {
+            ret = getLengthConstraints(wlk);
+            wlk = wlk.getBaseType();
+        }
+
+        return ret.isEmpty() ? typeLengthConstraints() : ret;
+    }
+
+    @Override
+    final T buildType() {
+        final List<LengthConstraint> baseLengths = findLenghts();
+
         if (lengthAlternatives == null || lengthAlternatives.isEmpty()) {
-            return baseLengthConstraints;
+            return buildType(baseLengths);
+        }
+
+        // Run through alternatives and resolve them against the base type
+        final List<LengthConstraint> resolvedLengths = ensureResolvedLengths(lengthAlternatives, baseLengths);
+
+        // 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);
+
+        // 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);
+            }
         }
 
-        // FIXME: calculate lengths
-        throw new UnsupportedOperationException();
+        return buildType(typedLengths);
     }
 }