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