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