Make RangeRestrictedTypeDefinition type-aware
[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.RangeRestrictedTypeDefinition;
25 import org.opendaylight.yangtools.yang.model.api.type.Uint16TypeDefinition;
26 import org.opendaylight.yangtools.yang.model.api.type.Uint32TypeDefinition;
27 import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition;
28 import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
29 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
30
31 /**
32  * Do not use this class outside of yangtools, its presence does not fall into the API stability contract.
33  */
34 @Beta
35 public abstract class AbstractIntegerStringCodec<N extends Number & Comparable<N>, T extends TypeDefinition<T>>
36         extends TypeDefinitionAwareCodec<N, T> {
37
38     private static final Pattern INT_PATTERN = Pattern.compile("[+-]?[1-9][0-9]*$");
39     private static final Pattern HEX_PATTERN = Pattern.compile("[+-]?0[xX][0-9a-fA-F]+");
40     private static final Pattern OCT_PATTERN = Pattern.compile("[+-]?0[1-7][0-7]*$");
41
42     // For up to two characters, this is very fast
43     private static final CharMatcher X_MATCHER = CharMatcher.anyOf("xX");
44
45     private static final String INCORRECT_LEXICAL_REPRESENTATION =
46             "Incorrect lexical representation of integer value: %s."
47                     + "\nAn integer value can be defined as: "
48                     + "\n  - a decimal number,"
49                     + "\n  - a hexadecimal number (prefix 0x)," + "%n  - an octal number (prefix 0)."
50                     + "\nSigned values are allowed. Spaces between digits are NOT allowed.";
51
52     private final RangeSet<N> rangeConstraints;
53
54     AbstractIntegerStringCodec(final Optional<T> typeDefinition, final Optional<RangeConstraint<N>> constraint,
55         final Class<N> outputClass) {
56         super(typeDefinition, outputClass);
57         rangeConstraints = constraint.map(RangeConstraint::getAllowedRanges).orElse(null);
58     }
59
60     public static AbstractIntegerStringCodec<?, ? extends IntegerTypeDefinition<?, ?>> from(
61             final IntegerTypeDefinition<?, ?> type) {
62         if (type instanceof Int8TypeDefinition) {
63             return new Int8StringCodec(Optional.of((Int8TypeDefinition) type));
64         } else if (type instanceof Int16TypeDefinition) {
65             return new Int16StringCodec(Optional.of((Int16TypeDefinition) type));
66         } else if (type instanceof Int32TypeDefinition) {
67             return new Int32StringCodec(Optional.of((Int32TypeDefinition) type));
68         } else if (type instanceof Int64TypeDefinition) {
69             return new Int64StringCodec(Optional.of((Int64TypeDefinition) type));
70         } else {
71             throw new IllegalArgumentException("Unsupported type: " + type);
72         }
73     }
74
75     public static AbstractIntegerStringCodec<?, ? extends UnsignedIntegerTypeDefinition<?, ?>> from(
76             final UnsignedIntegerTypeDefinition<?, ?> type) {
77         if (type instanceof Uint8TypeDefinition) {
78             return new Uint8StringCodec(Optional.of((Uint8TypeDefinition) type));
79         } else if (type instanceof Uint16TypeDefinition) {
80             return new Uint16StringCodec(Optional.of((Uint16TypeDefinition) type));
81         } else if (type instanceof Uint32TypeDefinition) {
82             return new Uint32StringCodec(Optional.of((Uint32TypeDefinition) type));
83         } else if (type instanceof Uint64TypeDefinition) {
84             return new Uint64StringCodec(Optional.of((Uint64TypeDefinition) type));
85         } else {
86             throw new IllegalArgumentException("Unsupported type: " + type);
87         }
88     }
89
90     @Override
91     public final N deserialize(final String stringRepresentation) {
92         final int base = provideBase(stringRepresentation);
93         final N deserialized;
94         if (base == 16) {
95             deserialized = deserialize(normalizeHexadecimal(stringRepresentation),base);
96         } else {
97             deserialized = deserialize(stringRepresentation,base);
98         }
99         validate(deserialized);
100         return deserialized;
101     }
102
103     /**
104      * Deserializes value from supplied string representation is supplied radix. See
105      * {@link Integer#parseInt(String, int)} for in-depth description about string and radix relationship.
106      *
107      * @param stringRepresentation String representation
108      * @param radix numeric base.
109      * @return Deserialized value.
110      */
111     abstract N deserialize(String stringRepresentation, int radix);
112
113     private void validate(final N value) {
114         if (rangeConstraints != null) {
115             checkArgument(rangeConstraints.contains(value), "Value '%s'  is not in required ranges %s",
116                 value, rangeConstraints);
117         }
118     }
119
120     protected static <N extends Number & Comparable<N>> Optional<RangeConstraint<N>> extractRange(
121             final RangeRestrictedTypeDefinition<?, N> type) {
122         return type == null ? Optional.empty() : type.getRangeConstraint();
123     }
124
125     private static int provideBase(final String integer) {
126         checkArgument(integer != null, "String representing integer number cannot be NULL");
127
128         if (integer.length() == 1 && integer.charAt(0) == '0') {
129             return 10;
130         } else if (INT_PATTERN.matcher(integer).matches()) {
131             return 10;
132         } else if (HEX_PATTERN.matcher(integer).matches()) {
133             return 16;
134         } else if (OCT_PATTERN.matcher(integer).matches()) {
135             return 8;
136         } else {
137             throw new NumberFormatException(String.format(INCORRECT_LEXICAL_REPRESENTATION, integer));
138         }
139     }
140
141     private static String normalizeHexadecimal(final String hexInt) {
142         checkArgument(hexInt != null, "String representing integer number in Hexadecimal format cannot be NULL!");
143         return X_MATCHER.removeFrom(hexInt);
144     }
145 }