Introduce formatting methods for SourceException
[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.collect.ImmutableSet;
14 import com.google.common.collect.Iterables;
15 import java.math.BigDecimal;
16 import java.math.BigInteger;
17 import java.util.ArrayList;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Set;
21 import org.opendaylight.yangtools.yang.common.QName;
22 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
23 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
24 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
25 import org.opendaylight.yangtools.yang.model.util.UnresolvedNumber;
26 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
27 import org.opendaylight.yangtools.yang.parser.spi.meta.QNameCacheNamespace;
28 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
29 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
30 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.type.LengthConstraintEffectiveImpl;
31 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.type.RangeConstraintEffectiveImpl;
32
33 /**
34 * util class for manipulating YANG base and extended types implementation
35 */
36 public final class TypeUtils {
37
38     public static final String BINARY = "binary";
39     public static final String BITS = "bits";
40     public static final String BOOLEAN = "boolean";
41     public static final String DECIMAL64 = "decimal64";
42     public static final String EMPTY = "empty";
43     public static final String ENUMERATION = "enumeration";
44     public static final String IDENTITY_REF = "identityref";
45     public static final String INSTANCE_IDENTIFIER = "instance-identifier";
46     public static final String INT8 = "int8";
47     public static final String INT16 = "int16";
48     public static final String INT32 = "int32";
49     public static final String INT64 = "int64";
50     public static final String LEAF_REF = "leafref";
51     public static final String STRING = "string";
52     public static final String UINT8 = "uint8";
53     public static final String UINT16 = "uint16";
54     public static final String UINT32 = "uint32";
55     public static final String UINT64 = "uint64";
56     public static final String UNION = "union";
57
58     private static final Set<String> BUILT_IN_TYPES =
59             ImmutableSet.of(BINARY, BITS, BOOLEAN, DECIMAL64, EMPTY, ENUMERATION, IDENTITY_REF, INSTANCE_IDENTIFIER,
60                     INT8, INT16, INT32, INT64, LEAF_REF, STRING, UINT8, UINT16, UINT32, UINT64, UNION);
61
62     private static final Set<String> TYPE_BODY_STMTS = ImmutableSet.of(
63         DECIMAL64, ENUMERATION, LEAF_REF, IDENTITY_REF, BITS, UNION);
64     private static final Splitter PIPE_SPLITTER = Splitter.on('|').trimResults();
65     private static final Splitter TWO_DOTS_SPLITTER = Splitter.on("..").trimResults();
66
67     private TypeUtils() {
68     }
69
70     private static BigDecimal yangConstraintToBigDecimal(final Number number) {
71         if (UnresolvedNumber.max().equals(number)) {
72             return RangeStatementImpl.YANG_MAX_NUM;
73         }
74         if (UnresolvedNumber.min().equals(number)) {
75             return RangeStatementImpl.YANG_MIN_NUM;
76         }
77
78         return new BigDecimal(number.toString());
79     }
80
81     private static int compareNumbers(final Number n1, final Number n2) {
82
83         final BigDecimal num1 = yangConstraintToBigDecimal(n1);
84         final BigDecimal num2 = yangConstraintToBigDecimal(n2);
85
86         return new BigDecimal(num1.toString()).compareTo(new BigDecimal(num2.toString()));
87     }
88
89     private static Number parseIntegerConstraintValue(final StmtContext<?, ?, ?> ctx, final String value) {
90         if ("max".equals(value)) {
91             return UnresolvedNumber.max();
92         }
93         if ("min".equals(value)) {
94             return UnresolvedNumber.min();
95         }
96
97         try {
98             return new BigInteger(value);
99         } catch (NumberFormatException e) {
100             throw new SourceException(String.format("Value %s is not a valid integer", value),
101                     ctx.getStatementSourceReference(), e);
102         }
103     }
104
105     private static Number parseDecimalConstraintValue(final StmtContext<?, ?, ?> ctx, final String value) {
106         if ("max".equals(value)) {
107             return UnresolvedNumber.max();
108         }
109         if ("min".equals(value)) {
110             return UnresolvedNumber.min();
111         }
112
113         try {
114             return value.indexOf('.') != -1 ? new BigDecimal(value) : new BigInteger(value);
115         } catch (NumberFormatException e) {
116             throw new SourceException(String.format("Value %s is not a valid decimal number", value),
117                     ctx.getStatementSourceReference(), e);
118         }
119     }
120
121     public static List<RangeConstraint> parseRangeListFromString(final StmtContext<?, ?, ?> ctx,
122                                                                  final String rangeArgument) {
123
124         Optional<String> description = Optional.absent();
125         Optional<String> reference = Optional.absent();
126
127         List<RangeConstraint> rangeConstraints = new ArrayList<>();
128
129         for (final String singleRange : PIPE_SPLITTER.split(rangeArgument)) {
130             final Iterator<String> boundaries = TWO_DOTS_SPLITTER.splitToList(singleRange).iterator();
131             final Number min = parseDecimalConstraintValue(ctx, boundaries.next());
132
133             final Number max;
134             if (boundaries.hasNext()) {
135                 max = parseDecimalConstraintValue(ctx, boundaries.next());
136
137                 // if min larger than max then error
138                 InferenceException.throwIf(compareNumbers(min, max) == 1, ctx.getStatementSourceReference(),
139                         "Range constraint %s has descending order of boundaries; should be ascending", singleRange);
140
141                 SourceException.throwIf(boundaries.hasNext(), ctx.getStatementSourceReference(),
142                     "Wrong number of boundaries in range constraint %s", singleRange);
143             } else {
144                 max = min;
145             }
146
147             // some of intervals overlapping
148             if (rangeConstraints.size() > 1 && compareNumbers(min, Iterables.getLast(rangeConstraints).getMax()) != 1) {
149                 throw new InferenceException(ctx.getStatementSourceReference(),
150                     "Some of the ranges in %s are not disjoint", rangeArgument);
151             }
152
153             rangeConstraints.add(new RangeConstraintEffectiveImpl(min, max, description, reference));
154         }
155
156         return rangeConstraints;
157     }
158
159     public static List<LengthConstraint> parseLengthListFromString(final StmtContext<?, ?, ?> ctx,
160             final String rangeArgument) {
161         Optional<String> description = Optional.absent();
162         Optional<String> reference = Optional.absent();
163
164         List<LengthConstraint> 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 = parseIntegerConstraintValue(ctx, boundaries.next());
169
170             final Number max;
171             if (boundaries.hasNext()) {
172                 max = parseIntegerConstraintValue(ctx, boundaries.next());
173
174                 // if min larger than max then error
175                 Preconditions.checkArgument(compareNumbers(min, max) != 1,
176                         "Length constraint %s has descending order of boundaries; should be ascending. Statement source at %s",
177                         singleRange, ctx.getStatementSourceReference());
178                 Preconditions.checkArgument(!boundaries.hasNext(),
179                         "Wrong number of boundaries in length constraint %s. Statement source at %s", singleRange,
180                         ctx.getStatementSourceReference());
181             } else {
182                 max = min;
183             }
184
185             // some of intervals overlapping
186             if (rangeConstraints.size() > 1 && compareNumbers(min, Iterables.getLast(rangeConstraints).getMax()) != 1) {
187                 throw new InferenceException(ctx.getStatementSourceReference(),
188                     "Some of the length ranges in %s are not disjoint", rangeArgument);
189             }
190
191             rangeConstraints.add(new LengthConstraintEffectiveImpl(min, max, description, reference));
192         }
193
194         return rangeConstraints;
195     }
196
197     public static boolean isYangTypeBodyStmtString(final String typeName) {
198         return TYPE_BODY_STMTS.contains(typeName);
199     }
200
201     public static boolean isYangBuiltInTypeString(final String typeName) {
202         return BUILT_IN_TYPES.contains(typeName);
203     }
204
205
206     public static SchemaPath typeEffectiveSchemaPath(final StmtContext<?, ?, ?> stmtCtx) {
207         final SchemaPath path = stmtCtx.getSchemaPath().get();
208         final QName qname = stmtCtx.getFromNamespace(QNameCacheNamespace.class,
209             QName.create(path.getParent().getLastComponent(), path.getLastComponent().getLocalName()));
210         return path.getParent().createChild(qname);
211     }
212 }