Make ConstraintMetaDefition attributes Optional
[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.Preconditions;
11 import com.google.common.base.Verify;
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.collect.ImmutableList.Builder;
14 import java.util.Collection;
15 import java.util.List;
16 import java.util.Optional;
17 import java.util.function.Function;
18 import javax.annotation.Nonnull;
19 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
20 import org.opendaylight.yangtools.yang.model.api.stmt.UnresolvedNumber;
21 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
22 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
23 import org.opendaylight.yangtools.yang.model.util.BaseConstraints;
24
25 public abstract class RangeRestrictedTypeBuilder<T extends RangeRestrictedTypeDefinition<T>>
26         extends AbstractRestrictedTypeBuilder<T> {
27     private List<RangeConstraint> rangeAlternatives;
28
29     RangeRestrictedTypeBuilder(final T baseType, final SchemaPath path) {
30         super(baseType, path);
31     }
32
33     public final void setRangeAlternatives(@Nonnull final Collection<RangeConstraint> rangeAlternatives) {
34         Preconditions.checkState(this.rangeAlternatives == null, "Range alternatives already defined as %s",
35                 this.rangeAlternatives);
36         this.rangeAlternatives = ImmutableList.copyOf(rangeAlternatives);
37         touch();
38     }
39
40     private static List<RangeConstraint> ensureResolvedRanges(final List<RangeConstraint> unresolved,
41             final List<RangeConstraint> baseRangeConstraints) {
42         // First check if we need to resolve anything at all
43         for (RangeConstraint c : unresolved) {
44             if (c.getMax() instanceof UnresolvedNumber || c.getMin() instanceof UnresolvedNumber) {
45                 return resolveRanges(unresolved, baseRangeConstraints);
46             }
47         }
48
49         // No need, just return the same list
50         return unresolved;
51     }
52
53     private static List<RangeConstraint> resolveRanges(final List<RangeConstraint> unresolved,
54             final List<RangeConstraint> baseRangeConstraints) {
55         final Builder<RangeConstraint> builder = ImmutableList.builder();
56
57         for (RangeConstraint c : unresolved) {
58             final Number max = c.getMax();
59             final Number min = c.getMin();
60
61             if (max instanceof UnresolvedNumber || min instanceof UnresolvedNumber) {
62                 final Number rMax = max instanceof UnresolvedNumber
63                     ?  ((UnresolvedNumber)max).resolveRange(baseRangeConstraints) : max;
64                 final Number rMin = min instanceof UnresolvedNumber
65                     ?  ((UnresolvedNumber)min).resolveRange(baseRangeConstraints) : min;
66
67                 builder.add(BaseConstraints.newRangeConstraint(rMin, rMax, Optional.ofNullable(c.getDescription()),
68                     Optional.ofNullable(c.getReference()), c.getErrorAppTag().orElse(null),
69                     c.getErrorMessage().orElse(null)));
70             } else {
71                 builder.add(c);
72             }
73
74         }
75
76         return builder.build();
77     }
78
79     private static List<RangeConstraint> ensureTypedRanges(final List<RangeConstraint> ranges,
80             final Class<? extends Number> clazz) {
81         for (RangeConstraint c : ranges) {
82             if (!clazz.isInstance(c.getMin()) || !clazz.isInstance(c.getMax())) {
83                 return typedRanges(ranges, clazz);
84             }
85         }
86
87         return ranges;
88     }
89
90     private static List<RangeConstraint> typedRanges(final List<RangeConstraint> ranges,
91             final Class<? extends Number> clazz) {
92         final Function<Number, ? extends Number> function = NumberUtil.converterTo(clazz);
93         Preconditions.checkArgument(function != null, "Unsupported range class %s", clazz);
94
95         final Builder<RangeConstraint> builder = ImmutableList.builder();
96
97         for (RangeConstraint c : ranges) {
98             if (!clazz.isInstance(c.getMin()) || !clazz.isInstance(c.getMax())) {
99                 final Number min;
100                 final Number max;
101
102                 try {
103                     min = function.apply(c.getMin());
104                     max = function.apply(c.getMax());
105                 } catch (NumberFormatException e) {
106                     throw new IllegalArgumentException(String.format("Constraint %s does not fit into range of %s",
107                         c, clazz.getSimpleName()), e);
108                 }
109                 builder.add(BaseConstraints.newRangeConstraint(min, max, Optional.ofNullable(c.getDescription()),
110                     Optional.ofNullable(c.getReference()), c.getErrorAppTag().orElse(null),
111                     c.getErrorMessage().orElse(null)));
112             } else {
113                 builder.add(c);
114             }
115         }
116
117         return builder.build();
118     }
119
120     private static boolean rangeCovered(final List<RangeConstraint> where,
121             final RangeConstraint what) {
122         for (RangeConstraint c : where) {
123             if (NumberUtil.isRangeCovered(what.getMin(), what.getMax(), c.getMin(), c.getMax())) {
124                 return true;
125             }
126         }
127
128         return false;
129     }
130
131     final List<RangeConstraint> calculateRangeConstraints(final List<RangeConstraint> baseRangeConstraints) {
132         if (rangeAlternatives == null || rangeAlternatives.isEmpty()) {
133             return baseRangeConstraints;
134         }
135
136         // Run through alternatives and resolve them against the base type
137         Verify.verify(!baseRangeConstraints.isEmpty(), "Base type %s does not define constraints", getBaseType());
138         final List<RangeConstraint> resolvedRanges = ensureResolvedRanges(rangeAlternatives, baseRangeConstraints);
139
140         // Next up, ensure the of boundaries match base constraints
141         final Class<? extends Number> clazz = baseRangeConstraints.get(0).getMin().getClass();
142         final List<RangeConstraint> typedRanges = ensureTypedRanges(resolvedRanges, clazz);
143
144         // Now verify if new ranges are strict subset of base ranges
145         for (RangeConstraint c : typedRanges) {
146             if (!rangeCovered(baseRangeConstraints, c)) {
147                 throw new InvalidRangeConstraintException(c,
148                         "Range constraint %s is not a subset of parent constraints %s", c, baseRangeConstraints);
149             }
150         }
151
152         return typedRanges;
153     }
154 }