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