Merge "BUG-2962: add DataTreeTip interface and implement it"
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / codec / AbstractIntegerStringCodec.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.data.impl.codec;
9
10 import static org.opendaylight.yangtools.yang.model.util.BaseTypes.INT16_QNAME;
11 import static org.opendaylight.yangtools.yang.model.util.BaseTypes.INT32_QNAME;
12 import static org.opendaylight.yangtools.yang.model.util.BaseTypes.INT64_QNAME;
13 import static org.opendaylight.yangtools.yang.model.util.BaseTypes.INT8_QNAME;
14 import static org.opendaylight.yangtools.yang.model.util.BaseTypes.UINT16_QNAME;
15 import static org.opendaylight.yangtools.yang.model.util.BaseTypes.UINT32_QNAME;
16 import static org.opendaylight.yangtools.yang.model.util.BaseTypes.UINT64_QNAME;
17 import static org.opendaylight.yangtools.yang.model.util.BaseTypes.UINT8_QNAME;
18
19 import com.google.common.base.CharMatcher;
20 import com.google.common.base.Optional;
21 import com.google.common.collect.Range;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
28 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
29 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
30 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
31
32 abstract class AbstractIntegerStringCodec<N extends Number & Comparable<N>, T extends TypeDefinition<T>> extends TypeDefinitionAwareCodec<N, T>{
33
34     private static final Pattern intPattern = Pattern.compile("[+-]?[1-9][0-9]*$");
35     private static final Pattern hexPattern = Pattern.compile("[+-]?0[xX][0-9a-fA-F]+");
36     private static final Pattern octalPattern = Pattern.compile("[+-]?0[1-7][0-7]*$");
37
38     // For up to two characters, this is very fast
39     private static final CharMatcher X_MATCHER = CharMatcher.anyOf("xX");
40
41     private static final String INCORRECT_LEXICAL_REPRESENTATION = "Incorrect lexical representation of integer value: %s."
42             + "\nAn integer value can be defined as: "
43             + "\n  - a decimal number,"
44             + "\n  - a hexadecimal number (prefix 0x)," + "%n  - an octal number (prefix 0)."
45             + "\nSigned values are allowed. Spaces between digits are NOT allowed.";
46
47
48     private final List<Range<N>> rangeConstraints;
49
50     protected AbstractIntegerStringCodec(final Optional<T> typeDefinition, final List<RangeConstraint> constraints , final Class<N> outputClass) {
51         super(typeDefinition, outputClass);
52         if(constraints.isEmpty()) {
53             rangeConstraints = Collections.emptyList();
54         } else {
55             final ArrayList<Range<N>> builder = new ArrayList<>(constraints.size());
56             for(final RangeConstraint yangConstraint : constraints) {
57                 builder.add(createRange(yangConstraint.getMin(),yangConstraint.getMax()));
58             }
59             rangeConstraints = builder;
60         }
61
62     }
63
64     static TypeDefinitionAwareCodec<?, IntegerTypeDefinition> from(final IntegerTypeDefinition type) {
65         final Optional<IntegerTypeDefinition> typeOptional = Optional.of(type);
66         IntegerTypeDefinition baseType = type;
67         while(baseType.getBaseType() != null) {
68             baseType = baseType.getBaseType();
69         }
70         if (INT8_QNAME.equals(baseType.getQName())) {
71             return new Int8StringCodec(typeOptional);
72         } else if (INT16_QNAME.equals(baseType.getQName())) {
73             return new Int16StringCodec(typeOptional);
74         } else if (INT32_QNAME.equals(baseType.getQName())) {
75             return new Int32StringCodec(typeOptional);
76         } else if (INT64_QNAME.equals(baseType.getQName())) {
77             return new Int64StringCodec(typeOptional);
78         }
79         throw new IllegalArgumentException("Unsupported base type: " + baseType.getQName());
80     }
81
82     static TypeDefinitionAwareCodec<?, UnsignedIntegerTypeDefinition> from(final UnsignedIntegerTypeDefinition type) {
83         final Optional<UnsignedIntegerTypeDefinition> typeOptional = Optional.of(type);
84         UnsignedIntegerTypeDefinition baseType = type;
85         while(baseType.getBaseType() != null) {
86             baseType = baseType.getBaseType();
87         }
88         if (UINT8_QNAME.equals(baseType.getQName())) {
89             return new Uint8StringCodec(typeOptional);
90         } else if (UINT16_QNAME.equals(baseType.getQName())) {
91             return new Uint16StringCodec(typeOptional);
92         } else if (UINT32_QNAME.equals(baseType.getQName())) {
93             return new Uint32StringCodec(typeOptional);
94         } else if (UINT64_QNAME.equals(baseType.getQName())) {
95             return new Uint64StringCodec(typeOptional);
96         }
97         throw new IllegalArgumentException("Unsupported base type: " + baseType.getQName());
98     }
99
100     private Range<N> createRange(final Number yangMin, final Number yangMax) {
101         final N min = convertValue(yangMin);
102         final N max = convertValue(yangMax);
103         return Range.closed(min, max);
104     }
105
106     @Override
107     public final N deserialize(final String stringRepresentation) {
108         final int base = provideBase(stringRepresentation);
109         final N deserialized;
110         if (base == 16) {
111             deserialized = deserialize(normalizeHexadecimal(stringRepresentation),base);
112         } else {
113             deserialized = deserialize(stringRepresentation,base);
114         }
115         validate(deserialized);
116         return deserialized;
117     }
118
119
120     private void validate(final N value) {
121         if (rangeConstraints.isEmpty()) {
122             return;
123         }
124         for (final Range<N> constraint : rangeConstraints) {
125             if (constraint.contains(value)) {
126                 return;
127             }
128         }
129         throw new IllegalArgumentException("Value '" + value + "'  is not in required range " + rangeConstraints);
130     }
131
132     /**
133      * Deserializes value from supplied string representation
134      * is supplied radix.
135      *
136      * See {@link Integer#parseInt(String, int)} for in-depth
137      * description about string and radix relationship.
138      *
139      * @param stringRepresentation String representation
140      * @param radix numeric base.
141      * @return Deserialized value.
142      */
143     protected abstract N deserialize(String stringRepresentation, int radix);
144
145     protected abstract N convertValue(Number value);
146
147
148     protected static List<RangeConstraint> extractRange(final IntegerTypeDefinition type) {
149         if(type == null) {
150             return Collections.emptyList();
151         }
152         return type.getRangeConstraints();
153     }
154
155     protected static List<RangeConstraint> extractRange(final UnsignedIntegerTypeDefinition type) {
156         if(type == null) {
157             return Collections.emptyList();
158         }
159         return type.getRangeConstraints();
160     }
161
162     private static int provideBase(final String integer) {
163         if (integer == null) {
164             throw new IllegalArgumentException("String representing integer number cannot be NULL");
165         }
166
167         if ((integer.length() == 1) && (integer.charAt(0) == '0')) {
168             return 10;
169         }
170
171         final Matcher intMatcher = intPattern.matcher(integer);
172         if (intMatcher.matches()) {
173             return 10;
174         }
175         final Matcher hexMatcher = hexPattern.matcher(integer);
176         if (hexMatcher.matches()) {
177             return 16;
178         }
179         final Matcher octMatcher = octalPattern.matcher(integer);
180         if (octMatcher.matches()) {
181             return 8;
182         }
183         final String formatedMessage =
184                 String.format(INCORRECT_LEXICAL_REPRESENTATION, integer);
185         throw new NumberFormatException(formatedMessage);
186     }
187
188     private static String normalizeHexadecimal(final String hexInt) {
189         if (hexInt == null) {
190             throw new IllegalArgumentException(
191                     "String representing integer number in Hexadecimal format cannot be NULL!");
192         }
193
194         return X_MATCHER.removeFrom(hexInt);
195     }
196 }