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