BUG-8043: correct RangeConstraint definition
[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 static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Verify;
15 import com.google.common.collect.ImmutableList;
16 import com.google.common.collect.ImmutableRangeSet;
17 import com.google.common.collect.ImmutableRangeSet.Builder;
18 import com.google.common.collect.Range;
19 import com.google.common.collect.RangeSet;
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.Optional;
23 import java.util.function.Function;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.opendaylight.yangtools.yang.model.api.ConstraintMetaDefinition;
26 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
27 import org.opendaylight.yangtools.yang.model.api.stmt.UnresolvedNumber;
28 import org.opendaylight.yangtools.yang.model.api.stmt.ValueRange;
29 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
30 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
31
32 public abstract class RangeRestrictedTypeBuilder<T extends RangeRestrictedTypeDefinition<T>>
33         extends AbstractRestrictedTypeBuilder<T> {
34     private ConstraintMetaDefinition constraint;
35     private List<ValueRange> ranges;
36
37     RangeRestrictedTypeBuilder(final T baseType, final SchemaPath path) {
38         super(baseType, path);
39     }
40
41     public final void setRangeConstraint(final @NonNull ConstraintMetaDefinition constraint,
42             final @NonNull List<ValueRange> ranges) {
43         checkState(this.ranges == null, "Range constraint already defined as %s %s", this.ranges, this.constraint);
44
45         this.constraint = requireNonNull(constraint);
46         this.ranges = ImmutableList.copyOf(ranges);
47         touch();
48     }
49
50     final <C extends Number & Comparable<C>> RangeConstraint<C> calculateRangeConstraint(
51             final RangeConstraint<C> baseRangeConstraint) {
52         if (ranges == null) {
53             return baseRangeConstraint;
54         }
55
56         // Run through alternatives and resolve them against the base type
57         final RangeSet<C> baseRangeSet = (RangeSet<C>) baseRangeConstraint.getAllowedRanges();
58         Verify.verify(!baseRangeSet.isEmpty(), "Base type %s does not define constraints", getBaseType());
59
60         final Range<? extends C> baseRange = baseRangeSet.span();
61         final List<ValueRange> resolvedRanges = ensureResolvedRanges(ranges, baseRange);
62
63         // Next up, ensure the of boundaries match base constraints
64         final RangeSet<C> typedRanges = ensureTypedRanges(resolvedRanges, baseRange.lowerEndpoint().getClass());
65
66         // Now verify if new ranges are strict subset of base ranges
67         if (!baseRangeSet.enclosesAll(typedRanges)) {
68             throw new InvalidRangeConstraintException(typedRanges,
69                 "Range constraints %s is not a subset of parent constraints %s", typedRanges, baseRangeSet);
70         }
71
72         return new RangeConstraint<C>() {
73             @Override
74             public Optional<String> getErrorAppTag() {
75                 return constraint.getErrorAppTag();
76             }
77
78             @Override
79             public Optional<String> getErrorMessage() {
80                 return constraint.getErrorMessage();
81             }
82
83             @Override
84             public Optional<String> getDescription() {
85                 return constraint.getDescription();
86             }
87
88             @Override
89             public Optional<String> getReference() {
90                 return constraint.getReference();
91             }
92
93             @Override
94             public RangeSet<? extends C> getAllowedRanges() {
95                 return typedRanges;
96             }
97         };
98     }
99
100     private static <C extends Number & Comparable<C>> List<ValueRange> ensureResolvedRanges(
101             final List<ValueRange> unresolved, final Range<? extends C> baseRange) {
102         // First check if we need to resolve anything at all
103         for (ValueRange c : unresolved) {
104             if (c.lowerBound() instanceof UnresolvedNumber || c.upperBound() instanceof UnresolvedNumber) {
105                 return resolveRanges(unresolved, baseRange);
106             }
107         }
108
109         // No need, just return the same list
110         return unresolved;
111     }
112
113     private static <T extends Number & Comparable<T>> List<ValueRange> resolveRanges(final List<ValueRange> unresolved,
114             final Range<? extends T> baseRange) {
115         final List<ValueRange> ret = new ArrayList<>(unresolved.size());
116         for (ValueRange range : unresolved) {
117             final Number min = range.lowerBound();
118             final Number max = range.upperBound();
119
120             if (max instanceof UnresolvedNumber || min instanceof UnresolvedNumber) {
121                 final Number rMin = min instanceof UnresolvedNumber
122                         ?  ((UnresolvedNumber)min).resolveRange(baseRange) : min;
123                 final Number rMax = max instanceof UnresolvedNumber
124                         ?  ((UnresolvedNumber)max).resolveRange(baseRange) : max;
125                 ret.add(ValueRange.of((@NonNull Number)rMin, (@NonNull Number)rMax));
126             } else {
127                 ret.add(range);
128             }
129         }
130
131         return ret;
132     }
133
134     private static <T extends Number & Comparable<T>> RangeSet<T> ensureTypedRanges(final List<ValueRange> ranges,
135             final Class<? extends Number> clazz) {
136         final Builder<T> builder = ImmutableRangeSet.builder();
137         for (ValueRange range : ranges) {
138             if (!clazz.isInstance(range.lowerBound()) || !clazz.isInstance(range.upperBound())) {
139                 return typedRanges(ranges, clazz);
140             }
141
142             builder.add(Range.closed((T) range.lowerBound(), (T)range.upperBound()));
143         }
144
145         return builder.build();
146     }
147
148     private static <T extends Number & Comparable<T>> RangeSet<T> typedRanges(final List<ValueRange> ranges,
149             final Class<? extends Number> clazz) {
150         final Function<Number, ? extends Number> function = NumberUtil.converterTo(clazz);
151         Preconditions.checkArgument(function != null, "Unsupported range class %s", clazz);
152
153         final Builder<T> builder = ImmutableRangeSet.builder();
154
155         for (ValueRange range : ranges) {
156             if (!clazz.isInstance(range.lowerBound()) || !clazz.isInstance(range.upperBound())) {
157                 final Number min;
158                 final Number max;
159
160                 try {
161                     min = function.apply(range.lowerBound());
162                     max = function.apply(range.upperBound());
163                 } catch (NumberFormatException e) {
164                     throw new IllegalArgumentException(String.format("Constraint %s does not fit into range of %s",
165                         range, clazz.getSimpleName()), e);
166                 }
167
168                 builder.add(Range.closed((T)min, (T)max));
169             } else {
170                 builder.add(Range.closed((T) range.lowerBound(), (T)range.upperBound()));
171             }
172         }
173
174         return builder.build();
175     }
176 }