YANGTOOLS-621: introduce specialized integer types
[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
12 import com.google.common.annotations.Beta;
13 import com.google.common.base.CharMatcher;
14 import com.google.common.collect.RangeSet;
15 import java.util.Optional;
16 import java.util.regex.Pattern;
17 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
18 import org.opendaylight.yangtools.yang.model.api.type.Int16TypeDefinition;
19 import org.opendaylight.yangtools.yang.model.api.type.Int32TypeDefinition;
20 import org.opendaylight.yangtools.yang.model.api.type.Int64TypeDefinition;
21 import org.opendaylight.yangtools.yang.model.api.type.Int8TypeDefinition;
22 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
23 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
24 import org.opendaylight.yangtools.yang.model.api.type.Uint16TypeDefinition;
25 import org.opendaylight.yangtools.yang.model.api.type.Uint32TypeDefinition;
26 import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition;
27 import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
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<?, ? extends IntegerTypeDefinition<?, ?>> from(
60             final IntegerTypeDefinition<?, ?> type) {
61         if (type instanceof Int8TypeDefinition) {
62             return new Int8StringCodec(Optional.of((Int8TypeDefinition) type));
63         } else if (type instanceof Int16TypeDefinition) {
64             return new Int16StringCodec(Optional.of((Int16TypeDefinition) type));
65         } else if (type instanceof Int32TypeDefinition) {
66             return new Int32StringCodec(Optional.of((Int32TypeDefinition) type));
67         } else if (type instanceof Int64TypeDefinition) {
68             return new Int64StringCodec(Optional.of((Int64TypeDefinition) type));
69         } else {
70             throw new IllegalArgumentException("Unsupported type: " + type);
71         }
72     }
73
74     public static AbstractIntegerStringCodec<?, ? extends UnsignedIntegerTypeDefinition<?, ?>> from(
75             final UnsignedIntegerTypeDefinition<?, ?> type) {
76         if (type instanceof Uint8TypeDefinition) {
77             return new Uint8StringCodec(Optional.of((Uint8TypeDefinition) type));
78         } else if (type instanceof Uint16TypeDefinition) {
79             return new Uint16StringCodec(Optional.of((Uint16TypeDefinition) type));
80         } else if (type instanceof Uint32TypeDefinition) {
81             return new Uint32StringCodec(Optional.of((Uint32TypeDefinition) type));
82         } else if (type instanceof Uint64TypeDefinition) {
83             return new Uint64StringCodec(Optional.of((Uint64TypeDefinition) type));
84         } else {
85             throw new IllegalArgumentException("Unsupported type: " + type);
86         }
87     }
88
89     @Override
90     public final N deserialize(final String stringRepresentation) {
91         final int base = provideBase(stringRepresentation);
92         final N deserialized;
93         if (base == 16) {
94             deserialized = deserialize(normalizeHexadecimal(stringRepresentation),base);
95         } else {
96             deserialized = deserialize(stringRepresentation,base);
97         }
98         validate(deserialized);
99         return deserialized;
100     }
101
102     /**
103      * Deserializes value from supplied string representation is supplied radix. See
104      * {@link Integer#parseInt(String, int)} for in-depth description about string and radix relationship.
105      *
106      * @param stringRepresentation String representation
107      * @param radix numeric base.
108      * @return Deserialized value.
109      */
110     abstract N deserialize(String stringRepresentation, int radix);
111
112     private void validate(final N value) {
113         if (rangeConstraints != null) {
114             checkArgument(rangeConstraints.contains(value), "Value '%s'  is not in required ranges %s",
115                 value, rangeConstraints);
116         }
117     }
118
119     protected static Optional<RangeConstraint<?>> extractRange(final IntegerTypeDefinition<?, ?> type) {
120         return type == null ? Optional.empty() : type.getRangeConstraint();
121     }
122
123     protected static Optional<RangeConstraint<?>> extractRange(final UnsignedIntegerTypeDefinition<?, ?> type) {
124         return type == null ? Optional.empty() : type.getRangeConstraint();
125     }
126
127     private static int provideBase(final String integer) {
128         checkArgument(integer != null, "String representing integer number cannot be NULL");
129
130         if (integer.length() == 1 && integer.charAt(0) == '0') {
131             return 10;
132         } else if (INT_PATTERN.matcher(integer).matches()) {
133             return 10;
134         } else if (HEX_PATTERN.matcher(integer).matches()) {
135             return 16;
136         } else if (OCT_PATTERN.matcher(integer).matches()) {
137             return 8;
138         } else {
139             throw new NumberFormatException(String.format(INCORRECT_LEXICAL_REPRESENTATION, integer));
140         }
141     }
142
143     private static String normalizeHexadecimal(final String hexInt) {
144         checkArgument(hexInt != null, "String representing integer number in Hexadecimal format cannot be NULL!");
145         return X_MATCHER.removeFrom(hexInt);
146     }
147 }