From 65a5738e522eddf43ac0af41974d37d2a9cf0545 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Sun, 18 Oct 2015 13:20:15 +0200 Subject: [PATCH] Add range validation and normalization 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 --- .../yang/model/util/type/NumberUtil.java | 107 +++++++++++++++ .../util/type/RangeRestrictedTypeBuilder.java | 124 +++++++++++++++++- 2 files changed, 226 insertions(+), 5 deletions(-) create mode 100644 yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/type/NumberUtil.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 index 0000000000..0d5e0b29c2 --- /dev/null +++ b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/type/NumberUtil.java @@ -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, Function> CONVERTERS; + static { + final ImmutableMap.Builder, Function> b = ImmutableMap.builder(); + b.put(Byte.class, new Function() { + @Override + public Number apply(final Number input) { + if (input instanceof Byte) { + return input; + } + + return Byte.valueOf(input.toString()); + } + }); + b.put(Short.class, new Function() { + @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() { + @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() { + @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() { + @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() { + @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 converterTo(final Class clazz) { + return CONVERTERS.get(clazz); + } +} diff --git a/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/type/RangeRestrictedTypeBuilder.java b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/type/RangeRestrictedTypeBuilder.java index e9bdd2ce77..7efc7686b4 100644 --- a/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/type/RangeRestrictedTypeBuilder.java +++ b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/type/RangeRestrictedTypeBuilder.java @@ -7,34 +7,148 @@ */ 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> extends AbstractRestrictedTypeBuilder { - private Collection rangeAlternatives; + private List rangeAlternatives; RangeRestrictedTypeBuilder(final T baseType, final SchemaPath path) { super(Preconditions.checkNotNull(baseType), path); } - public void setRangeAlternatives(@Nonnull final Collection rangeAlternatives) { + public final void setRangeAlternatives(@Nonnull final Collection 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 ensureResolvedRanges(final List unresolved, + final List 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 resolveRanges(final List unresolved, + final List baseRangeConstraints) { + final Builder 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 ensureTypedRanges(final List ranges, + final Class clazz) { + for (RangeConstraint c : ranges) { + if (!clazz.isInstance(c.getMin()) || !clazz.isInstance(c.getMax())) { + return typedRanges(ranges, clazz); + } + } + + return ranges; + } + + private static List typedRanges(final List ranges, final Class clazz) { + final Function function = NumberUtil.converterTo(clazz); + Preconditions.checkArgument(function != null, "Unsupported range class %s", clazz); + + final Builder 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 where, + final RangeConstraint what) { + // We have ensured the types match, and we are sure each of those types implements comparable + @SuppressWarnings("unchecked") + final Comparable min = (Comparable) what.getMin(); + @SuppressWarnings("unchecked") + final Comparable max = (Comparable) what.getMax(); + + for (RangeConstraint c : where) { + if (min.compareTo(c.getMin()) >= 0 && max.compareTo(c.getMax()) <= 0) { + return true; + } + } + + return false; + } + final List calculateRangeConstraints(final List 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 resolvedRanges = ensureResolvedRanges(rangeAlternatives, baseRangeConstraints); + + // Next up, ensure the of boundaries match base constraints + final Class clazz = baseRangeConstraints.get(0).getMin().getClass(); + final List 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; } } -- 2.36.6