Cleanup use of Guava library
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / util / type / LengthRestrictedTypeBuilder.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.collect.ImmutableList;
12 import com.google.common.collect.ImmutableList.Builder;
13 import java.util.Collection;
14 import java.util.List;
15 import java.util.Optional;
16 import java.util.function.Function;
17 import javax.annotation.Nonnull;
18 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
19 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
20 import org.opendaylight.yangtools.yang.model.api.type.LengthRestrictedTypeDefinition;
21 import org.opendaylight.yangtools.yang.model.util.BaseConstraints;
22 import org.opendaylight.yangtools.yang.model.util.UnresolvedNumber;
23
24 public abstract class LengthRestrictedTypeBuilder<T extends LengthRestrictedTypeDefinition<T>>
25         extends AbstractRestrictedTypeBuilder<T> {
26     private List<LengthConstraint> lengthAlternatives;
27
28     LengthRestrictedTypeBuilder(final T baseType, final SchemaPath path) {
29         super(Preconditions.checkNotNull(baseType), path);
30     }
31
32     public final void setLengthAlternatives(@Nonnull final Collection<LengthConstraint> lengthAlternatives) {
33         Preconditions.checkState(this.lengthAlternatives == null, "Range alternatives already defined as %s",
34                 lengthAlternatives);
35         this.lengthAlternatives = ImmutableList.copyOf(lengthAlternatives);
36         touch();
37     }
38
39     private static List<LengthConstraint> ensureResolvedLengths(final List<LengthConstraint> unresolved,
40             final List<LengthConstraint> baseRangeConstraints) {
41         // First check if we need to resolve anything at all
42         for (LengthConstraint c : unresolved) {
43             if (c.getMax() instanceof UnresolvedNumber || c.getMin() instanceof UnresolvedNumber) {
44                 return resolveLengths(unresolved, baseRangeConstraints);
45             }
46         }
47
48         // No need, just return the same list
49         return unresolved;
50     }
51
52     private static List<LengthConstraint> resolveLengths(final List<LengthConstraint> unresolved,
53             final List<LengthConstraint> baseLengthConstraints) {
54         final Builder<LengthConstraint> builder = ImmutableList.builder();
55
56         for (LengthConstraint 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).resolveLength(baseLengthConstraints) : max;
63                 final Number rMin = min instanceof UnresolvedNumber
64                     ? ((UnresolvedNumber)min).resolveLength(baseLengthConstraints) : min;
65
66                 builder.add(BaseConstraints.newLengthConstraint(rMin, rMax, Optional.ofNullable(c.getDescription()),
67                     Optional.ofNullable(c.getReference()), c.getErrorAppTag(), c.getErrorMessage()));
68             } else {
69                 builder.add(c);
70             }
71         }
72
73         return builder.build();
74     }
75
76     private static List<LengthConstraint> ensureTypedLengths(final List<LengthConstraint> lengths,
77             final Class<? extends Number> clazz) {
78         for (LengthConstraint c : lengths) {
79             if (!clazz.isInstance(c.getMin()) || !clazz.isInstance(c.getMax())) {
80                 return typedLengths(lengths, clazz);
81             }
82         }
83
84         return lengths;
85     }
86
87     private static List<LengthConstraint> typedLengths(final List<LengthConstraint> lengths,
88             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<LengthConstraint> builder = ImmutableList.builder();
93
94         for (LengthConstraint c : lengths) {
95             if (!clazz.isInstance(c.getMin()) || !clazz.isInstance(c.getMax())) {
96                 final Number min;
97                 final Number max;
98
99                 try {
100                     min = function.apply(c.getMin());
101                     max = function.apply(c.getMax());
102                 } catch (NumberFormatException e) {
103                     throw new IllegalArgumentException(String.format("Constraint %s does not fit into range of %s",
104                         c, clazz.getSimpleName()), e);
105                 }
106                 builder.add(BaseConstraints.newLengthConstraint(min, max, Optional.ofNullable(c.getDescription()),
107                     Optional.ofNullable(c.getReference()), c.getErrorAppTag(), c.getErrorMessage()));
108             } else {
109                 builder.add(c);
110             }
111         }
112
113         return builder.build();
114     }
115
116     private static boolean lengthCovered(final List<LengthConstraint> where,
117             final LengthConstraint what) {
118         for (LengthConstraint c : where) {
119             if (NumberUtil.isRangeCovered(what.getMin(), what.getMax(), c.getMin(), c.getMax())) {
120                 return true;
121             }
122         }
123
124         return false;
125     }
126
127     @Override
128     final T buildType() {
129         final List<LengthConstraint> baseLengths = findLenghts();
130
131         if (lengthAlternatives == null || lengthAlternatives.isEmpty()) {
132             return buildType(baseLengths);
133         }
134
135         // Run through alternatives and resolve them against the base type
136         final List<LengthConstraint> resolvedLengths = ensureResolvedLengths(lengthAlternatives, baseLengths);
137
138         // Next up, ensure the of boundaries match base constraints
139         final Class<? extends Number> clazz = baseLengths.get(0).getMin().getClass();
140         final List<LengthConstraint> typedLengths = ensureTypedLengths(resolvedLengths, clazz);
141
142         // Now verify if new ranges are strict subset of base ranges
143         for (LengthConstraint c : typedLengths) {
144             if (!lengthCovered(baseLengths, c)) {
145                 throw new InvalidLengthConstraintException(c,
146                         "Length constraint %s is not a subset of parent constraints %s", c, baseLengths);
147             }
148         }
149
150         return buildType(typedLengths);
151     }
152
153     abstract T buildType(List<LengthConstraint> lengthConstraints);
154
155     abstract List<LengthConstraint> typeLengthConstraints();
156
157     private List<LengthConstraint> findLenghts() {
158         List<LengthConstraint> ret = ImmutableList.of();
159         T wlk = getBaseType();
160         while (wlk != null && ret.isEmpty()) {
161             ret = wlk.getLengthConstraints();
162             wlk = wlk.getBaseType();
163         }
164
165         return ret.isEmpty() ? typeLengthConstraints() : ret;
166     }
167 }