Add range validation and normalization 72/28572/3
authorRobert Varga <robert.varga@pantheon.sk>
Sun, 18 Oct 2015 11:20:15 +0000 (13:20 +0200)
committerGerrit Code Review <gerrit@opendaylight.org>
Tue, 20 Oct 2015 08:23:13 +0000 (08:23 +0000)
RangeRestrictedTypeBuilder now computes proper ranges, lowered to the
class defined by the base type, so we have the same types for
enforcement no matter the input.

Change-Id: Ibc139a3ff3d18bba87219a1fe91b2269b107a3c6
Signed-off-by: Robert Varga <robert.varga@pantheon.sk>
yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/type/NumberUtil.java [new file with mode: 0644]
yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/type/RangeRestrictedTypeBuilder.java

diff --git a/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/type/NumberUtil.java b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/type/NumberUtil.java
new file mode 100644 (file)
index 0000000..0d5e0b2
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2015 Pantheon Technologies s.r.o. 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.model.util.type;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Map;
+
+final class NumberUtil {
+    private static final Map<Class<? extends Number>, Function<Number, Number>> CONVERTERS;
+    static {
+        final ImmutableMap.Builder<Class<? extends Number>, Function<Number, Number>> b = ImmutableMap.builder();
+        b.put(Byte.class, new Function<Number, Number>() {
+            @Override
+            public Number apply(final Number input) {
+                if (input instanceof Byte) {
+                    return input;
+                }
+
+                return Byte.valueOf(input.toString());
+            }
+        });
+        b.put(Short.class, new Function<Number, Number>() {
+            @Override
+            public Number apply(final Number input) {
+                if (input instanceof Short) {
+                    return input;
+                }
+                if (input instanceof Byte) {
+                    return input.shortValue();
+                }
+
+                return Short.valueOf(input.toString());
+            }
+        });
+        b.put(Integer.class, new Function<Number, Number>() {
+            @Override
+            public Number apply(final Number input) {
+                if (input instanceof Integer) {
+                    return input;
+                }
+                if (input instanceof Byte || input instanceof Short) {
+                    return input.intValue();
+                }
+
+                return Integer.valueOf(input.toString());
+            }
+        });
+        b.put(Long.class, new Function<Number, Number>() {
+            @Override
+            public Number apply(final Number input) {
+                if (input instanceof Long) {
+                    return input;
+                }
+                if (input instanceof Byte || input instanceof Short || input instanceof Integer) {
+                    return input.longValue();
+                }
+
+                return Long.valueOf(input.toString());
+            }
+        });
+        b.put(BigDecimal.class, new Function<Number, Number>() {
+            @Override
+            public Number apply(final Number input) {
+                if (input instanceof BigDecimal) {
+                    return input;
+                }
+                if (input instanceof Byte || input instanceof Short ||
+                        input instanceof Integer || input instanceof Long) {
+                    return BigDecimal.valueOf(input.longValue());
+                }
+
+                return new BigDecimal(input.toString());
+            }
+        });
+        b.put(BigInteger.class, new Function<Number, Number>() {
+            @Override
+            public Number apply(final Number input) {
+                if (input instanceof BigInteger) {
+                    return input;
+                }
+                if (input instanceof Byte || input instanceof Short ||
+                        input instanceof Integer || input instanceof Long) {
+                    return BigInteger.valueOf(input.longValue());
+                }
+
+                return new BigInteger(input.toString());
+            }
+        });
+        CONVERTERS = b.build();
+    }
+
+    private NumberUtil() {
+        throw new UnsupportedOperationException();
+    }
+
+    static Function<Number, Number> converterTo(final Class<? extends Number> clazz) {
+        return CONVERTERS.get(clazz);
+    }
+}
index e9bdd2ce776f6bfe75a885c8c100166377c38d3d..7efc7686b4bf3373dc8c00ac2c2e6a285d470d8d 100644 (file)
  */
 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.base.Verify;
+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.RangeConstraint;
+import org.opendaylight.yangtools.yang.model.util.BaseConstraints;
+import org.opendaylight.yangtools.yang.model.util.UnresolvedNumber;
 
 public abstract class RangeRestrictedTypeBuilder<T extends TypeDefinition<T>> extends AbstractRestrictedTypeBuilder<T> {
-    private Collection<RangeConstraint> rangeAlternatives;
+    private List<RangeConstraint> rangeAlternatives;
 
     RangeRestrictedTypeBuilder(final T baseType, final SchemaPath path) {
         super(Preconditions.checkNotNull(baseType), path);
     }
 
-    public void setRangeAlternatives(@Nonnull final Collection<RangeConstraint> rangeAlternatives) {
+    public final void setRangeAlternatives(@Nonnull final Collection<RangeConstraint> rangeAlternatives) {
         Preconditions.checkState(this.rangeAlternatives == null, "Range alternatives already defined as %s",
                 this.rangeAlternatives);
-        this.rangeAlternatives = Preconditions.checkNotNull(rangeAlternatives);
+        this.rangeAlternatives = ImmutableList.copyOf(rangeAlternatives);
         touch();
     }
 
+    private static List<RangeConstraint> ensureResolvedRanges(final List<RangeConstraint> unresolved,
+            final List<RangeConstraint> baseRangeConstraints) {
+        // First check if we need to resolve anything at all
+        for (RangeConstraint c : unresolved) {
+            if (c.getMax() instanceof UnresolvedNumber || c.getMin() instanceof UnresolvedNumber) {
+                return resolveRanges(unresolved, baseRangeConstraints);
+            }
+        }
+
+        // No need, just return the same list
+        return unresolved;
+    }
+
+    private static List<RangeConstraint> resolveRanges(final List<RangeConstraint> unresolved,
+            final List<RangeConstraint> baseRangeConstraints) {
+        final Builder<RangeConstraint> builder = ImmutableList.builder();
+
+        for (RangeConstraint 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).resolve(baseRangeConstraints) : max;
+                final Number rMin = min instanceof UnresolvedNumber ?
+                        ((UnresolvedNumber)min).resolve(baseRangeConstraints) : min;
+
+                builder.add(BaseConstraints.newRangeConstraint(rMin, rMax, Optional.fromNullable(c.getDescription()),
+                    Optional.fromNullable(c.getReference())));
+            } else {
+                builder.add(c);
+            }
+
+        }
+
+        return builder.build();
+    }
+
+    private static List<RangeConstraint> ensureTypedRanges(final List<RangeConstraint> ranges,
+            final Class<? extends Number> clazz) {
+        for (RangeConstraint c : ranges) {
+            if (!clazz.isInstance(c.getMin()) || !clazz.isInstance(c.getMax())) {
+                return typedRanges(ranges, clazz);
+            }
+        }
+
+        return ranges;
+    }
+
+    private static List<RangeConstraint> typedRanges(final List<RangeConstraint> ranges, final Class<? extends Number> clazz) {
+        final Function<Number, Number> function = NumberUtil.converterTo(clazz);
+        Preconditions.checkArgument(function != null, "Unsupported range class %s", clazz);
+
+        final Builder<RangeConstraint> builder = ImmutableList.builder();
+
+        for (RangeConstraint c : ranges) {
+            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.newRangeConstraint(min, max, Optional.fromNullable(c.getDescription()),
+                    Optional.fromNullable(c.getReference())));
+            } else {
+                builder.add(c);
+            }
+        }
+
+        return builder.build();
+    }
+
+    private static boolean rangeCovered(final List<RangeConstraint> where,
+            final RangeConstraint what) {
+        // We have ensured the types match, and we are sure each of those types implements comparable
+        @SuppressWarnings("unchecked")
+        final Comparable<Object> min = (Comparable<Object>) what.getMin();
+        @SuppressWarnings("unchecked")
+        final Comparable<Object> max = (Comparable<Object>) what.getMax();
+
+        for (RangeConstraint c : where) {
+            if (min.compareTo(c.getMin()) >= 0 && max.compareTo(c.getMax()) <= 0) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     final List<RangeConstraint> calculateRangeConstraints(final List<RangeConstraint> baseRangeConstraints) {
         if (rangeAlternatives == null || rangeAlternatives.isEmpty()) {
             return baseRangeConstraints;
         }
 
-        // FIXME: calculate ranges
-        throw new UnsupportedOperationException();
+        // Run through alternatives and resolve them against the base type
+        Verify.verify(!baseRangeConstraints.isEmpty(), "Base type %s does not define constraints", getBaseType());
+        final List<RangeConstraint> resolvedRanges = ensureResolvedRanges(rangeAlternatives, baseRangeConstraints);
+
+        // Next up, ensure the of boundaries match base constraints
+        final Class<? extends Number> clazz = baseRangeConstraints.get(0).getMin().getClass();
+        final List<RangeConstraint> typedRanges = ensureTypedRanges(resolvedRanges, clazz);
+
+        // Now verify if new ranges are strict subset of base ranges
+        for (RangeConstraint c : typedRanges) {
+            Preconditions.checkArgument(rangeCovered(baseRangeConstraints, c),
+                "Range constraint %s is not a subset of parent constraints %s", c, baseRangeConstraints);
+        }
+
+        // FIXME: merge adjacent ranges and sort them
+        return typedRanges;
     }
 }