Add range validation and normalization
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / util / type / RangeRestrictedTypeBuilder.java
1 /*
2  * Copyright (c) 2015 Pantheon Technologies s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.model.util.type;
9
10 import com.google.common.base.Function;
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import com.google.common.base.Verify;
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.ImmutableList.Builder;
16 import java.util.Collection;
17 import java.util.List;
18 import javax.annotation.Nonnull;
19 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
20 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
21 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
22 import org.opendaylight.yangtools.yang.model.util.BaseConstraints;
23 import org.opendaylight.yangtools.yang.model.util.UnresolvedNumber;
24
25 public abstract class RangeRestrictedTypeBuilder<T extends TypeDefinition<T>> extends AbstractRestrictedTypeBuilder<T> {
26     private List<RangeConstraint> rangeAlternatives;
27
28     RangeRestrictedTypeBuilder(final T baseType, final SchemaPath path) {
29         super(Preconditions.checkNotNull(baseType), path);
30     }
31
32     public final void setRangeAlternatives(@Nonnull final Collection<RangeConstraint> rangeAlternatives) {
33         Preconditions.checkState(this.rangeAlternatives == null, "Range alternatives already defined as %s",
34                 this.rangeAlternatives);
35         this.rangeAlternatives = ImmutableList.copyOf(rangeAlternatives);
36         touch();
37     }
38
39     private static List<RangeConstraint> ensureResolvedRanges(final List<RangeConstraint> unresolved,
40             final List<RangeConstraint> baseRangeConstraints) {
41         // First check if we need to resolve anything at all
42         for (RangeConstraint c : unresolved) {
43             if (c.getMax() instanceof UnresolvedNumber || c.getMin() instanceof UnresolvedNumber) {
44                 return resolveRanges(unresolved, baseRangeConstraints);
45             }
46         }
47
48         // No need, just return the same list
49         return unresolved;
50     }
51
52     private static List<RangeConstraint> resolveRanges(final List<RangeConstraint> unresolved,
53             final List<RangeConstraint> baseRangeConstraints) {
54         final Builder<RangeConstraint> builder = ImmutableList.builder();
55
56         for (RangeConstraint c : unresolved) {
57             final Number max = c.getMax();
58             final Number min = c.getMin();
59
60             if (max instanceof UnresolvedNumber || min instanceof UnresolvedNumber) {
61                 final Number rMax = max instanceof UnresolvedNumber ?
62                         ((UnresolvedNumber)max).resolve(baseRangeConstraints) : max;
63                 final Number rMin = min instanceof UnresolvedNumber ?
64                         ((UnresolvedNumber)min).resolve(baseRangeConstraints) : min;
65
66                 builder.add(BaseConstraints.newRangeConstraint(rMin, rMax, Optional.fromNullable(c.getDescription()),
67                     Optional.fromNullable(c.getReference())));
68             } else {
69                 builder.add(c);
70             }
71
72         }
73
74         return builder.build();
75     }
76
77     private static List<RangeConstraint> ensureTypedRanges(final List<RangeConstraint> ranges,
78             final Class<? extends Number> clazz) {
79         for (RangeConstraint c : ranges) {
80             if (!clazz.isInstance(c.getMin()) || !clazz.isInstance(c.getMax())) {
81                 return typedRanges(ranges, clazz);
82             }
83         }
84
85         return ranges;
86     }
87
88     private static List<RangeConstraint> typedRanges(final List<RangeConstraint> ranges, final Class<? extends Number> clazz) {
89         final Function<Number, Number> function = NumberUtil.converterTo(clazz);
90         Preconditions.checkArgument(function != null, "Unsupported range class %s", clazz);
91
92         final Builder<RangeConstraint> builder = ImmutableList.builder();
93
94         for (RangeConstraint c : ranges) {
95             if (!clazz.isInstance(c.getMin()) || !clazz.isInstance(c.getMax())) {
96                 final Number min, max;
97
98                 try {
99                     min = function.apply(c.getMin());
100                     max = function.apply(c.getMax());
101                 } catch (NumberFormatException e) {
102                     throw new IllegalArgumentException(String.format("Constraint %s does not fit into range of %s",
103                         c, clazz.getSimpleName()), e);
104                 }
105                 builder.add(BaseConstraints.newRangeConstraint(min, max, Optional.fromNullable(c.getDescription()),
106                     Optional.fromNullable(c.getReference())));
107             } else {
108                 builder.add(c);
109             }
110         }
111
112         return builder.build();
113     }
114
115     private static boolean rangeCovered(final List<RangeConstraint> where,
116             final RangeConstraint what) {
117         // We have ensured the types match, and we are sure each of those types implements comparable
118         @SuppressWarnings("unchecked")
119         final Comparable<Object> min = (Comparable<Object>) what.getMin();
120         @SuppressWarnings("unchecked")
121         final Comparable<Object> max = (Comparable<Object>) what.getMax();
122
123         for (RangeConstraint c : where) {
124             if (min.compareTo(c.getMin()) >= 0 && max.compareTo(c.getMax()) <= 0) {
125                 return true;
126             }
127         }
128
129         return false;
130     }
131
132     final List<RangeConstraint> calculateRangeConstraints(final List<RangeConstraint> baseRangeConstraints) {
133         if (rangeAlternatives == null || rangeAlternatives.isEmpty()) {
134             return baseRangeConstraints;
135         }
136
137         // Run through alternatives and resolve them against the base type
138         Verify.verify(!baseRangeConstraints.isEmpty(), "Base type %s does not define constraints", getBaseType());
139         final List<RangeConstraint> resolvedRanges = ensureResolvedRanges(rangeAlternatives, baseRangeConstraints);
140
141         // Next up, ensure the of boundaries match base constraints
142         final Class<? extends Number> clazz = baseRangeConstraints.get(0).getMin().getClass();
143         final List<RangeConstraint> typedRanges = ensureTypedRanges(resolvedRanges, clazz);
144
145         // Now verify if new ranges are strict subset of base ranges
146         for (RangeConstraint c : typedRanges) {
147             Preconditions.checkArgument(rangeCovered(baseRangeConstraints, c),
148                 "Range constraint %s is not a subset of parent constraints %s", c, baseRangeConstraints);
149         }
150
151         // FIXME: merge adjacent ranges and sort them
152         return typedRanges;
153     }
154 }