Bug 2362: Added range validation as last part of deserialization.
[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 com.google.common.base.CharMatcher;
11 import com.google.common.base.Optional;
12 import com.google.common.collect.Range;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.regex.Matcher;
17 import java.util.regex.Pattern;
18 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
19 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
20 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
21 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
22
23 abstract class AbstractIntegerStringCodec<N extends Number & Comparable<N>, T extends TypeDefinition<T>> extends TypeDefinitionAwareCodec<N, T>{
24
25     private static final Pattern intPattern = Pattern.compile("[+-]?[1-9][0-9]*$");
26     private static final Pattern hexPattern = Pattern.compile("[+-]?0[xX][0-9a-fA-F]+");
27     private static final Pattern octalPattern = Pattern.compile("[+-]?0[1-7][0-7]*$");
28
29     // For up to two characters, this is very fast
30     private static final CharMatcher X_MATCHER = CharMatcher.anyOf("xX");
31
32     private static final String INCORRECT_LEXICAL_REPRESENTATION = "Incorrect lexical representation of integer value: %s."
33             + "\nAn integer value can be defined as: "
34             + "\n  - a decimal number,"
35             + "\n  - a hexadecimal number (prefix 0x)," + "%n  - an octal number (prefix 0)."
36             + "\nSigned values are allowed. Spaces between digits are NOT allowed.";
37
38
39     private final List<Range<N>> rangeConstraints;
40
41     protected AbstractIntegerStringCodec(final Optional<T> typeDefinition, final List<RangeConstraint> constraints , final Class<N> outputClass) {
42         super(typeDefinition, outputClass);
43         if(constraints.isEmpty()) {
44             rangeConstraints = Collections.emptyList();
45         } else {
46             final ArrayList<Range<N>> builder = new ArrayList<>(constraints.size());
47             for(final RangeConstraint yangConstraint : constraints) {
48                 builder.add(createRange(yangConstraint.getMin(),yangConstraint.getMax()));
49             }
50             rangeConstraints = builder;
51         }
52
53     }
54
55     private Range<N> createRange(final Number yangMin, final Number yangMax) {
56         final N min = convertValue(yangMin);
57         final N max = convertValue(yangMax);
58         return Range.closed(min, max);
59     }
60
61     @Override
62     public final N deserialize(final String stringRepresentation) {
63         final int base = provideBase(stringRepresentation);
64         final N deserialized;
65         if (base == 16) {
66             deserialized = deserialize(normalizeHexadecimal(stringRepresentation),base);
67         } else {
68             deserialized = deserialize(stringRepresentation,base);
69         }
70         validate(deserialized);
71         return deserialized;
72     }
73
74
75     private final void validate(final N value) {
76         if (rangeConstraints.isEmpty()) {
77             return;
78         }
79         for (final Range<N> constraint : rangeConstraints) {
80             if (constraint.contains(value)) {
81                 return;
82             }
83         }
84         // FIXME: Provide better error report.
85         throw new IllegalArgumentException("Value is not in required range.");
86     }
87
88     /**
89      * Deserializes value from supplied string representation
90      * is supplied radix.
91      *
92      * See {@link Integer#parseInt(String, int)} for in-depth
93      * description about string and radix relationship.
94      *
95      * @param stringRepresentation String representation
96      * @param radix numeric base.
97      * @return Deserialized value.
98      */
99     protected abstract N deserialize(String stringRepresentation, int radix);
100
101     protected abstract N convertValue(Number value);
102
103
104     protected static List<RangeConstraint> extractRange(final IntegerTypeDefinition type) {
105         if(type == null) {
106             return Collections.emptyList();
107         }
108         return type.getRangeConstraints();
109     }
110
111     protected static List<RangeConstraint> extractRange(final UnsignedIntegerTypeDefinition type) {
112         if(type == null) {
113             return Collections.emptyList();
114         }
115         return type.getRangeConstraints();
116     }
117
118     private static final int provideBase(final String integer) {
119         if (integer == null) {
120             throw new IllegalArgumentException("String representing integer number cannot be NULL");
121         }
122
123         if ((integer.length() == 1) && (integer.charAt(0) == '0')) {
124             return 10;
125         }
126
127         final Matcher intMatcher = intPattern.matcher(integer);
128         if (intMatcher.matches()) {
129             return 10;
130         }
131         final Matcher hexMatcher = hexPattern.matcher(integer);
132         if (hexMatcher.matches()) {
133             return 16;
134         }
135         final Matcher octMatcher = octalPattern.matcher(integer);
136         if (octMatcher.matches()) {
137             return 8;
138         }
139         final String formatedMessage =
140                 String.format(INCORRECT_LEXICAL_REPRESENTATION, integer);
141         throw new NumberFormatException(formatedMessage);
142     }
143
144     private static String normalizeHexadecimal(final String hexInt) {
145         if (hexInt == null) {
146             throw new IllegalArgumentException(
147                     "String representing integer number in Hexadecimal format cannot be NULL!");
148         }
149
150         return X_MATCHER.removeFrom(hexInt);
151     }
152 }