Populate data/ hierarchy
[yangtools.git] / model / yang-model-ri / src / main / java / org / opendaylight / yangtools / yang / model / ri / 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.ri.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.function.Function;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.model.api.ConstraintMetaDefinition;
26 import org.opendaylight.yangtools.yang.model.api.stmt.UnresolvedNumber;
27 import org.opendaylight.yangtools.yang.model.api.stmt.ValueRange;
28 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
29 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
30
31 public abstract class RangeRestrictedTypeBuilder<T extends RangeRestrictedTypeDefinition<T, N>,
32         N extends Number & Comparable<N>> extends AbstractRestrictedTypeBuilder<T> {
33     private ConstraintMetaDefinition constraint;
34     private ImmutableList<ValueRange> ranges;
35
36     RangeRestrictedTypeBuilder(final T baseType, final QName qname) {
37         super(baseType, qname);
38     }
39
40     @SuppressWarnings("checkstyle:hiddenField")
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 RangeConstraint<N> calculateRangeConstraint(final RangeConstraint<N> baseRangeConstraint) {
51         if (ranges == null) {
52             return baseRangeConstraint;
53         }
54
55         // Run through alternatives and resolve them against the base type
56         final RangeSet<N> baseRangeSet = baseRangeConstraint.getAllowedRanges();
57         Verify.verify(!baseRangeSet.isEmpty(), "Base type %s does not define constraints", getBaseType());
58
59         final Range<N> baseRange = baseRangeSet.span();
60         final List<ValueRange> resolvedRanges = ensureResolvedRanges(ranges, baseRange);
61
62         // Next up, ensure the of boundaries match base constraints
63         final RangeSet<N> typedRanges = ensureTypedRanges(resolvedRanges, baseRange.lowerEndpoint().getClass());
64
65         // Now verify if new ranges are strict subset of base ranges
66         if (!baseRangeSet.enclosesAll(typedRanges)) {
67             throw new InvalidRangeConstraintException(typedRanges,
68                 "Range constraint %s is not a subset of parent constraint %s", typedRanges, baseRangeSet);
69         }
70
71         return new ResolvedRangeConstraint<>(constraint, typedRanges);
72     }
73
74     private static <C extends Number & Comparable<C>> List<ValueRange> ensureResolvedRanges(
75             final List<ValueRange> unresolved, final Range<C> baseRange) {
76         // First check if we need to resolve anything at all
77         for (ValueRange c : unresolved) {
78             if (c.lowerBound() instanceof UnresolvedNumber || c.upperBound() instanceof UnresolvedNumber) {
79                 return resolveRanges(unresolved, baseRange);
80             }
81         }
82
83         // No need, just return the same list
84         return unresolved;
85     }
86
87     private static <T extends Number & Comparable<T>> List<ValueRange> resolveRanges(final List<ValueRange> unresolved,
88             final Range<T> baseRange) {
89         final List<ValueRange> ret = new ArrayList<>(unresolved.size());
90         for (ValueRange range : unresolved) {
91             final Number min = range.lowerBound();
92             final Number max = range.upperBound();
93
94             if (max instanceof UnresolvedNumber || min instanceof UnresolvedNumber) {
95                 final @NonNull Number rMin = min instanceof UnresolvedNumber
96                         ?  ((UnresolvedNumber)min).resolveRange(baseRange) : min;
97                 final @NonNull Number rMax = max instanceof UnresolvedNumber
98                         ?  ((UnresolvedNumber)max).resolveRange(baseRange) : max;
99                 ret.add(ValueRange.of(rMin, rMax));
100             } else {
101                 ret.add(range);
102             }
103         }
104
105         return ret;
106     }
107
108     @SuppressWarnings("unchecked")
109     private static <T extends Number & Comparable<T>> RangeSet<T> ensureTypedRanges(final List<ValueRange> ranges,
110             final Class<? extends Number> clazz) {
111         final Builder<T> builder = ImmutableRangeSet.builder();
112         for (ValueRange range : ranges) {
113             if (!clazz.isInstance(range.lowerBound()) || !clazz.isInstance(range.upperBound())) {
114                 return typedRanges(ranges, clazz);
115             }
116
117             builder.add(Range.closed((T) range.lowerBound(), (T)range.upperBound()));
118         }
119
120         return builder.build();
121     }
122
123     @SuppressWarnings("unchecked")
124     private static <T extends Number & Comparable<T>> RangeSet<T> typedRanges(final List<ValueRange> ranges,
125             final Class<? extends Number> clazz) {
126         final Function<Number, ? extends Number> function = NumberUtil.converterTo(clazz);
127         Preconditions.checkArgument(function != null, "Unsupported range class %s", clazz);
128
129         final Builder<T> builder = ImmutableRangeSet.builder();
130
131         for (ValueRange range : ranges) {
132             if (!clazz.isInstance(range.lowerBound()) || !clazz.isInstance(range.upperBound())) {
133                 final Number min;
134                 final Number max;
135
136                 try {
137                     min = function.apply(range.lowerBound());
138                     max = function.apply(range.upperBound());
139                 } catch (NumberFormatException e) {
140                     throw new IllegalArgumentException(String.format("Constraint %s does not fit into range of %s",
141                         range, clazz.getSimpleName()), e);
142                 }
143
144                 builder.add(Range.closed((T)min, (T)max));
145             } else {
146                 builder.add(Range.closed((T) range.lowerBound(), (T)range.upperBound()));
147             }
148         }
149
150         return builder.build();
151     }
152 }