159a28f3b4f871b11ed551273f9970c3bf0a15e1
[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.RangeSet;
23 import java.util.Optional;
24 import java.util.regex.Pattern;
25 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
26 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
27 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
28 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
29
30 /**
31  * Do not use this class outside of yangtools, its presence does not fall into the API stability contract.
32  */
33 @Beta
34 public abstract class AbstractIntegerStringCodec<N extends Number & Comparable<N>, T extends TypeDefinition<T>>
35         extends TypeDefinitionAwareCodec<N, T> {
36
37     private static final Pattern INT_PATTERN = Pattern.compile("[+-]?[1-9][0-9]*$");
38     private static final Pattern HEX_PATTERN = Pattern.compile("[+-]?0[xX][0-9a-fA-F]+");
39     private static final Pattern OCT_PATTERN = Pattern.compile("[+-]?0[1-7][0-7]*$");
40
41     // For up to two characters, this is very fast
42     private static final CharMatcher X_MATCHER = CharMatcher.anyOf("xX");
43
44     private static final String INCORRECT_LEXICAL_REPRESENTATION =
45             "Incorrect lexical representation of integer value: %s."
46                     + "\nAn integer value can be defined as: "
47                     + "\n  - a decimal number,"
48                     + "\n  - a hexadecimal number (prefix 0x)," + "%n  - an octal number (prefix 0)."
49                     + "\nSigned values are allowed. Spaces between digits are NOT allowed.";
50
51     private final RangeSet<N> rangeConstraints;
52
53     AbstractIntegerStringCodec(final Optional<T> typeDefinition, final Optional<RangeConstraint<?>> constraint,
54         final Class<N> outputClass) {
55         super(typeDefinition, outputClass);
56         rangeConstraints = (RangeSet<N>) constraint.map(RangeConstraint::getAllowedRanges).orElse(null);
57     }
58
59     public static AbstractIntegerStringCodec<?, IntegerTypeDefinition> from(final IntegerTypeDefinition type) {
60         // FIXME: this is not necessary with yang.model.util.type
61         IntegerTypeDefinition baseType = type;
62         while (baseType.getBaseType() != null) {
63             baseType = baseType.getBaseType();
64         }
65
66         final Optional<IntegerTypeDefinition> typeOptional = Optional.of(type);
67
68         // FIXME: use DerivedTypes#isInt8() and friends
69         if (INT8_QNAME.equals(baseType.getQName())) {
70             return new Int8StringCodec(typeOptional);
71         } else if (INT16_QNAME.equals(baseType.getQName())) {
72             return new Int16StringCodec(typeOptional);
73         } else if (INT32_QNAME.equals(baseType.getQName())) {
74             return new Int32StringCodec(typeOptional);
75         } else if (INT64_QNAME.equals(baseType.getQName())) {
76             return new Int64StringCodec(typeOptional);
77         } else {
78             throw new IllegalArgumentException("Unsupported base type: " + baseType.getQName());
79         }
80     }
81
82     public static AbstractIntegerStringCodec<?, UnsignedIntegerTypeDefinition> from(
83             final UnsignedIntegerTypeDefinition type) {
84         // FIXME: this is not necessary with yang.model.util.type
85         UnsignedIntegerTypeDefinition baseType = type;
86         while (baseType.getBaseType() != null) {
87             baseType = baseType.getBaseType();
88         }
89
90         final Optional<UnsignedIntegerTypeDefinition> typeOptional = Optional.of(type);
91
92         // FIXME: use DerivedTypes#isUint8() and friends
93         if (UINT8_QNAME.equals(baseType.getQName())) {
94             return new Uint8StringCodec(typeOptional);
95         } else if (UINT16_QNAME.equals(baseType.getQName())) {
96             return new Uint16StringCodec(typeOptional);
97         } else if (UINT32_QNAME.equals(baseType.getQName())) {
98             return new Uint32StringCodec(typeOptional);
99         } else if (UINT64_QNAME.equals(baseType.getQName())) {
100             return new Uint64StringCodec(typeOptional);
101         } else {
102             throw new IllegalArgumentException("Unsupported base type: " + baseType.getQName());
103         }
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      * Deserializes value from supplied string representation is supplied radix. See
121      * {@link Integer#parseInt(String, int)} for in-depth description about string and radix relationship.
122      *
123      * @param stringRepresentation String representation
124      * @param radix numeric base.
125      * @return Deserialized value.
126      */
127     abstract N deserialize(String stringRepresentation, int radix);
128
129     private void validate(final N value) {
130         if (rangeConstraints != null) {
131             checkArgument(rangeConstraints.contains(value), "Value '%s'  is not in required ranges %s",
132                 value, rangeConstraints);
133         }
134     }
135
136     protected static Optional<RangeConstraint<?>> extractRange(final IntegerTypeDefinition type) {
137         return type == null ? Optional.empty() : type.getRangeConstraint();
138     }
139
140     protected static Optional<RangeConstraint<?>> extractRange(final UnsignedIntegerTypeDefinition type) {
141         return type == null ? Optional.empty() : type.getRangeConstraint();
142     }
143
144     private static int provideBase(final String integer) {
145         checkArgument(integer != null, "String representing integer number cannot be NULL");
146
147         if (integer.length() == 1 && integer.charAt(0) == '0') {
148             return 10;
149         } else if (INT_PATTERN.matcher(integer).matches()) {
150             return 10;
151         } else if (HEX_PATTERN.matcher(integer).matches()) {
152             return 16;
153         } else if (OCT_PATTERN.matcher(integer).matches()) {
154             return 8;
155         } else {
156             throw new NumberFormatException(String.format(INCORRECT_LEXICAL_REPRESENTATION, integer));
157         }
158     }
159
160     private static String normalizeHexadecimal(final String hexInt) {
161         checkArgument(hexInt != null, "String representing integer number in Hexadecimal format cannot be NULL!");
162         return X_MATCHER.removeFrom(hexInt);
163     }
164 }