BUG-7052: Move RecursiveObjectLeader to util
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / rfc6020 / TypeUtils.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. 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.parser.stmt.rfc6020;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Splitter;
13 import com.google.common.base.Strings;
14 import com.google.common.collect.ImmutableMap;
15 import com.google.common.collect.ImmutableMap.Builder;
16 import com.google.common.collect.ImmutableSet;
17 import com.google.common.collect.Iterables;
18 import java.math.BigDecimal;
19 import java.math.BigInteger;
20 import java.util.ArrayList;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.common.YangVersion;
28 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
29 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
30 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
31 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
32 import org.opendaylight.yangtools.yang.model.api.stmt.TypeEffectiveStatement;
33 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
34 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
35 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
36 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
37 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
38 import org.opendaylight.yangtools.yang.model.util.UnresolvedNumber;
39 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
40 import org.opendaylight.yangtools.yang.parser.spi.meta.QNameCacheNamespace;
41 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
42 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
43 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.type.LengthConstraintEffectiveImpl;
44 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.type.RangeConstraintEffectiveImpl;
45
46 /**
47 * util class for manipulating YANG base and extended types implementation
48 */
49 public final class TypeUtils {
50
51     public static final String BINARY = "binary";
52     public static final String BITS = "bits";
53     public static final String BOOLEAN = "boolean";
54     public static final String DECIMAL64 = "decimal64";
55     public static final String EMPTY = "empty";
56     public static final String ENUMERATION = "enumeration";
57     public static final String IDENTITY_REF = "identityref";
58     public static final String INSTANCE_IDENTIFIER = "instance-identifier";
59     public static final String INT8 = "int8";
60     public static final String INT16 = "int16";
61     public static final String INT32 = "int32";
62     public static final String INT64 = "int64";
63     public static final String LEAF_REF = "leafref";
64     public static final String STRING = "string";
65     public static final String UINT8 = "uint8";
66     public static final String UINT16 = "uint16";
67     public static final String UINT32 = "uint32";
68     public static final String UINT64 = "uint64";
69     public static final String UNION = "union";
70
71     private static final Map<String, String> BUILT_IN_TYPES;
72
73     static {
74         final Builder<String, String> b = ImmutableMap.builder();
75
76         b.put(BINARY, BINARY);
77         b.put(BITS, BITS);
78         b.put(BOOLEAN, BOOLEAN);
79         b.put(DECIMAL64, DECIMAL64);
80         b.put(EMPTY, EMPTY);
81         b.put(ENUMERATION, ENUMERATION);
82         b.put(IDENTITY_REF,IDENTITY_REF);
83         b.put(INSTANCE_IDENTIFIER, INSTANCE_IDENTIFIER);
84         b.put(INT8, INT8);
85         b.put(INT16, INT16);
86         b.put(INT32, INT32);
87         b.put(INT64, INT64);
88         b.put(LEAF_REF, LEAF_REF);
89         b.put(STRING, STRING);
90         b.put(UINT8, UINT8);
91         b.put(UINT16, UINT16);
92         b.put(UINT32, UINT32);
93         b.put(UINT64, UINT64);
94         b.put(UNION, UNION);
95
96         BUILT_IN_TYPES = b.build();
97     }
98
99     private static final Set<String> TYPE_BODY_STMTS = ImmutableSet.of(
100         DECIMAL64, ENUMERATION, LEAF_REF, IDENTITY_REF, BITS, UNION);
101     private static final Splitter PIPE_SPLITTER = Splitter.on('|').trimResults();
102     private static final Splitter TWO_DOTS_SPLITTER = Splitter.on("..").trimResults();
103
104     private TypeUtils() {
105     }
106
107     private static BigDecimal yangConstraintToBigDecimal(final Number number) {
108         if (UnresolvedNumber.max().equals(number)) {
109             return RangeStatementImpl.YANG_MAX_NUM;
110         }
111         if (UnresolvedNumber.min().equals(number)) {
112             return RangeStatementImpl.YANG_MIN_NUM;
113         }
114
115         return new BigDecimal(number.toString());
116     }
117
118     private static int compareNumbers(final Number n1, final Number n2) {
119
120         final BigDecimal num1 = yangConstraintToBigDecimal(n1);
121         final BigDecimal num2 = yangConstraintToBigDecimal(n2);
122
123         return new BigDecimal(num1.toString()).compareTo(new BigDecimal(num2.toString()));
124     }
125
126     private static Number parseIntegerConstraintValue(final StmtContext<?, ?, ?> ctx, final String value) {
127         if ("max".equals(value)) {
128             return UnresolvedNumber.max();
129         }
130         if ("min".equals(value)) {
131             return UnresolvedNumber.min();
132         }
133
134         try {
135             return new BigInteger(value);
136         } catch (final NumberFormatException e) {
137             throw new SourceException(String.format("Value %s is not a valid integer", value),
138                     ctx.getStatementSourceReference(), e);
139         }
140     }
141
142     private static Number parseDecimalConstraintValue(final StmtContext<?, ?, ?> ctx, final String value) {
143         if ("max".equals(value)) {
144             return UnresolvedNumber.max();
145         }
146         if ("min".equals(value)) {
147             return UnresolvedNumber.min();
148         }
149
150         try {
151             return value.indexOf('.') != -1 ? new BigDecimal(value) : new BigInteger(value);
152         } catch (final NumberFormatException e) {
153             throw new SourceException(String.format("Value %s is not a valid decimal number", value),
154                     ctx.getStatementSourceReference(), e);
155         }
156     }
157
158     public static List<RangeConstraint> parseRangeListFromString(final StmtContext<?, ?, ?> ctx,
159                                                                  final String rangeArgument) {
160
161         final Optional<String> description = Optional.absent();
162         final Optional<String> reference = Optional.absent();
163
164         final List<RangeConstraint> rangeConstraints = new ArrayList<>();
165
166         for (final String singleRange : PIPE_SPLITTER.split(rangeArgument)) {
167             final Iterator<String> boundaries = TWO_DOTS_SPLITTER.splitToList(singleRange).iterator();
168             final Number min = parseDecimalConstraintValue(ctx, boundaries.next());
169
170             final Number max;
171             if (boundaries.hasNext()) {
172                 max = parseDecimalConstraintValue(ctx, boundaries.next());
173
174                 // if min larger than max then error
175                 InferenceException.throwIf(compareNumbers(min, max) == 1, ctx.getStatementSourceReference(),
176                         "Range constraint %s has descending order of boundaries; should be ascending", singleRange);
177
178                 SourceException.throwIf(boundaries.hasNext(), ctx.getStatementSourceReference(),
179                     "Wrong number of boundaries in range constraint %s", singleRange);
180             } else {
181                 max = min;
182             }
183
184             // some of intervals overlapping
185             if (rangeConstraints.size() > 1 && compareNumbers(min, Iterables.getLast(rangeConstraints).getMax()) != 1) {
186                 throw new InferenceException(ctx.getStatementSourceReference(),
187                     "Some of the ranges in %s are not disjoint", rangeArgument);
188             }
189
190             rangeConstraints.add(new RangeConstraintEffectiveImpl(min, max, description, reference));
191         }
192
193         return rangeConstraints;
194     }
195
196     public static List<LengthConstraint> parseLengthListFromString(final StmtContext<?, ?, ?> ctx,
197             final String lengthArgument) {
198         final Optional<String> description = Optional.absent();
199         final Optional<String> reference = Optional.absent();
200
201         final List<LengthConstraint> lengthConstraints = new ArrayList<>();
202
203         for (final String singleRange : PIPE_SPLITTER.split(lengthArgument)) {
204             final Iterator<String> boundaries = TWO_DOTS_SPLITTER.splitToList(singleRange).iterator();
205             final Number min = parseIntegerConstraintValue(ctx, boundaries.next());
206
207             final Number max;
208             if (boundaries.hasNext()) {
209                 max = parseIntegerConstraintValue(ctx, boundaries.next());
210
211                 // if min larger than max then error
212                 Preconditions.checkArgument(compareNumbers(min, max) != 1,
213                         "Length constraint %s has descending order of boundaries; should be ascending. Statement source at %s",
214                         singleRange, ctx.getStatementSourceReference());
215                 Preconditions.checkArgument(!boundaries.hasNext(),
216                         "Wrong number of boundaries in length constraint %s. Statement source at %s", singleRange,
217                         ctx.getStatementSourceReference());
218             } else {
219                 max = min;
220             }
221
222             // some of intervals overlapping
223             if (lengthConstraints.size() > 1 && compareNumbers(min, Iterables.getLast(lengthConstraints).getMax()) != 1) {
224                 throw new InferenceException(ctx.getStatementSourceReference(),
225                     "Some of the length ranges in %s are not disjoint", lengthArgument);
226             }
227
228             lengthConstraints.add(new LengthConstraintEffectiveImpl(min, max, description, reference));
229         }
230
231         return lengthConstraints;
232     }
233
234     public static boolean isYangTypeBodyStmtString(final String typeName) {
235         return TYPE_BODY_STMTS.contains(typeName);
236     }
237
238     public static boolean isYangBuiltInTypeString(final String typeName) {
239         return BUILT_IN_TYPES.containsKey(typeName);
240     }
241
242
243     public static SchemaPath typeEffectiveSchemaPath(final StmtContext<?, ?, ?> stmtCtx) {
244         final SchemaPath path = stmtCtx.getSchemaPath().get();
245         final SchemaPath parent = path.getParent();
246         final QName parentQName = parent.getLastComponent();
247         Preconditions.checkArgument(parentQName != null, "Path %s has an empty parent", path);
248
249         final QName qname = stmtCtx.getFromNamespace(QNameCacheNamespace.class,
250             QName.create(parentQName, path.getLastComponent().getLocalName()));
251         return parent.createChild(qname);
252     }
253
254     /**
255      * Checks whether supplied type has any of specified default values marked
256      * with an if-feature. This method creates mutable copy of supplied set of
257      * default values.
258      *
259      * @param yangVersion
260      *            yang version
261      * @param typeStmt
262      *            type statement which should be checked
263      * @param defaultValues
264      *            set of default values which should be checked. The method
265      *            creates mutable copy of this set
266      *
267      * @return true if any of specified default values is marked with an
268      *         if-feature, otherwise false
269      *
270      * @throws IllegalStateException
271      *             if any of specified default values has not been found
272      */
273     public static boolean hasDefaultValueMarkedWithIfFeature(final YangVersion yangVersion,
274             final TypeEffectiveStatement<?> typeStmt, final Set<String> defaultValues) {
275         return !defaultValues.isEmpty() && yangVersion == YangVersion.VERSION_1_1
276                 && isRelevantForIfFeatureCheck(typeStmt)
277                 && isAnyDefaultValueMarkedWithIfFeature(typeStmt, new HashSet<>(defaultValues));
278     }
279
280     /**
281      * Checks whether supplied type has specified default value marked with an
282      * if-feature. This method creates mutable set of supplied default value.
283      *
284      * @param yangVersion
285      *            yang version
286      * @param typeStmt
287      *            type statement which should be checked
288      * @param defaultValue
289      *            default value to be checked
290      *
291      * @return true if specified default value is marked with an if-feature,
292      *         otherwise false
293      *
294      * @throws IllegalStateException
295      *             if specified default value has not been found
296      */
297     public static boolean hasDefaultValueMarkedWithIfFeature(final YangVersion yangVersion,
298             final TypeEffectiveStatement<?> typeStmt, final String defaultValue) {
299         final HashSet<String> defaultValues = new HashSet<>();
300         defaultValues.add(defaultValue);
301         return !Strings.isNullOrEmpty(defaultValue) && yangVersion == YangVersion.VERSION_1_1
302                 && isRelevantForIfFeatureCheck(typeStmt)
303                 && isAnyDefaultValueMarkedWithIfFeature(typeStmt, defaultValues);
304     }
305
306     static String findBuiltinString(final String rawArgument) {
307         return BUILT_IN_TYPES.get(rawArgument);
308     }
309
310     private static boolean isRelevantForIfFeatureCheck(final TypeEffectiveStatement<?> typeStmt) {
311         final TypeDefinition<?> typeDefinition = typeStmt.getTypeDefinition();
312         return typeDefinition instanceof EnumTypeDefinition || typeDefinition instanceof BitsTypeDefinition
313                 || typeDefinition instanceof UnionTypeDefinition;
314     }
315
316     private static boolean isAnyDefaultValueMarkedWithIfFeature(final TypeEffectiveStatement<?> typeStmt,
317             final Set<String> defaultValues) {
318         for (final EffectiveStatement<?, ?> effectiveSubstatement : typeStmt.effectiveSubstatements()) {
319             if (YangStmtMapping.BIT.equals(effectiveSubstatement.statementDefinition())) {
320                 final QName bitQName = (QName) effectiveSubstatement.argument();
321                 if (defaultValues.remove(bitQName.getLocalName()) && containsIfFeature(effectiveSubstatement)) {
322                     return true;
323                 }
324             } else if (YangStmtMapping.ENUM.equals(effectiveSubstatement.statementDefinition())
325                     && defaultValues.remove(effectiveSubstatement.argument())
326                     && containsIfFeature(effectiveSubstatement)) {
327                 return true;
328             } else if (effectiveSubstatement instanceof TypeEffectiveStatement) {
329                 return isAnyDefaultValueMarkedWithIfFeature((TypeEffectiveStatement<?>) effectiveSubstatement,
330                         defaultValues);
331             }
332         }
333
334         Preconditions.checkState(defaultValues.isEmpty(), "Unable to find following default values %s", defaultValues);
335         return false;
336     }
337
338     private static boolean containsIfFeature(final EffectiveStatement<?, ?> effectiveStatement) {
339         for (final EffectiveStatement<?, ?> effectiveSubstatement : effectiveStatement.effectiveSubstatements()) {
340             if (YangStmtMapping.IF_FEATURE.equals(effectiveSubstatement.statementDefinition())) {
341                 return true;
342             }
343         }
344         return false;
345     }
346 }