Add HexString/DottedQuad/Uuid support to IetfYangUtil
[mdsal.git] / model / ietf / ietf-type-util / src / main / java / org / opendaylight / mdsal / model / ietf / util / AbstractIetfYangUtil.java
1 /*
2  * Copyright (c) 2016 Pantheon Technologies s.r.o. 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.mdsal.model.ietf.util;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import com.google.common.annotations.Beta;
13 import java.util.Arrays;
14 import java.util.UUID;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.opendaylight.mdsal.binding.spec.reflect.StringValueObjectFactory;
17
18 /**
19  * Abstract utility class for dealing with MAC addresses as defined in the ietf-yang-types model. This class is
20  * used by revision-specific classes.
21  *
22  * @param <M> mac-address type
23  * @param <P> phys-address type
24  */
25 @Beta
26 public abstract class AbstractIetfYangUtil<M, P, H, Q, U> {
27     private static final int MAC_BYTE_LENGTH = 6;
28     private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
29     private static final byte @NonNull[] EMPTY_BYTES = new byte[0];
30     private static final byte @NonNull[] HEX_VALUES;
31
32     static {
33         final byte[] b = new byte['f' + 1];
34         Arrays.fill(b, (byte)-1);
35
36         for (char c = '0'; c <= '9'; ++c) {
37             b[c] = (byte)(c - '0');
38         }
39         for (char c = 'A'; c <= 'F'; ++c) {
40             b[c] = (byte)(c - 'A' + 10);
41         }
42         for (char c = 'a'; c <= 'f'; ++c) {
43             b[c] = (byte)(c - 'a' + 10);
44         }
45
46         HEX_VALUES = b;
47     }
48
49     private final StringValueObjectFactory<M> macFactory;
50     private final StringValueObjectFactory<P> physFactory;
51     private final StringValueObjectFactory<H> hexFactory;
52     private final StringValueObjectFactory<Q> quadFactory;
53     private final StringValueObjectFactory<U> uuidFactory;
54
55     protected AbstractIetfYangUtil(final Class<M> macClass, final Class<P> physClass, final Class<H> hexClass,
56             final Class<Q> quadClass, final Class<U> uuidClass) {
57         this.macFactory = StringValueObjectFactory.create(macClass, "00:00:00:00:00:00");
58         this.physFactory = StringValueObjectFactory.create(physClass, "00:00");
59         this.hexFactory = StringValueObjectFactory.create(hexClass, "00");
60         this.quadFactory = StringValueObjectFactory.create(quadClass, "0.0.0.0");
61         this.uuidFactory = StringValueObjectFactory.create(uuidClass, "f81d4fae-7dec-11d0-a765-00a0c91e6bf6");
62     }
63
64     /**
65      * Convert the value of a MacAddress into the canonical representation.
66      *
67      * @param macAddress Input MAC address
68      * @return A MacAddress containing the canonical representation.
69      * @throws NullPointerException if macAddress is null
70      */
71     public final @NonNull M canonizeMacAddress(final @NonNull M macAddress) {
72         final char[] input = getValue(macAddress).toCharArray();
73         return ensureLowerCase(input) ? macFactory.newInstance(String.valueOf(input)) : macAddress;
74     }
75
76     /**
77      * Create a MacAddress object holding the canonical representation of the 6 bytes
78      * passed in as argument.
79      * @param bytes 6-byte input array
80      * @return MacAddress with canonical string derived from input bytes
81      * @throws NullPointerException if bytes is null
82      * @throws IllegalArgumentException if length of input is not 6 bytes
83      */
84     public final @NonNull M macAddressFor(final byte @NonNull[] bytes) {
85         checkArgument(bytes.length == MAC_BYTE_LENGTH, "MAC address should have 6 bytes, not %s",
86                 bytes.length);
87         return macFactory.newInstance(bytesToString(bytes, 17));
88     }
89
90     public final byte @NonNull[] macAddressBytes(final @NonNull M macAddress) {
91         return stringToBytes(getValue(macAddress), MAC_BYTE_LENGTH);
92     }
93
94     /**
95      * Convert the value of a PhysAddress into the canonical representation.
96      *
97      * @param physAddress Input MAC address
98      * @return A PhysAddress containing the canonical representation.
99      * @throws NullPointerException if physAddress is null
100      */
101     public final @NonNull P canonizePhysAddress(final @NonNull P physAddress) {
102         final char[] input = getPhysValue(physAddress).toCharArray();
103         return ensureLowerCase(input) ? physFactory.newInstance(String.valueOf(input)) : physAddress;
104     }
105
106     /**
107      * Create a PhysAddress object holding the canonical representation of the bytes passed in as argument.
108      *
109      * @param bytes input array
110      * @return PhysAddress with canonical string derived from input bytes
111      * @throws NullPointerException if bytes is null
112      * @throws IllegalArgumentException if length of input is not at least 1 byte
113      */
114     public final @NonNull P physAddressFor(final byte @NonNull[] bytes) {
115         checkArgument(bytes.length > 0, "Physical address should have at least one byte");
116         return physFactory.newInstance(bytesToString(bytes, bytes.length * 3 - 1));
117     }
118
119     public final byte @NonNull[] physAddressBytes(final @NonNull P physAddress) {
120         final String str = getPhysValue(physAddress);
121         return str.isEmpty() ? EMPTY_BYTES : stringToBytes(str, str.length() / 3 + 1);
122     }
123
124     @Deprecated
125     public final byte @NonNull[] bytesFor(final @NonNull M macAddress) {
126         return macAddressBytes(macAddress);
127     }
128
129     public final @NonNull H hexStringFor(final byte @NonNull[] bytes) {
130         checkArgument(bytes.length > 0, "Hex string should have at least one byte");
131         return hexFactory.newInstance(bytesToString(bytes, bytes.length * 3 - 1));
132     }
133
134     public final byte @NonNull[] hexStringBytes(final @NonNull H hexString) {
135         final String str = getHexValue(hexString);
136         return stringToBytes(str, str.length() / 3 + 1);
137     }
138
139     public final @NonNull Q dottedQuadFor(final byte @NonNull[] bytes) {
140         checkArgument(bytes.length == 4, "Dotted-quad should have 4 bytes");
141         return quadFactory.newInstance(AbstractIetfInetUtil.addressStringV4(bytes));
142     }
143
144     public final byte @NonNull[] dottedQuadBytes(final @NonNull Q hexString) {
145         final String str = getQuadValue(hexString);
146         final byte[] bytes = new byte[4];
147         Ipv4Utils.fillIpv4Bytes(bytes, 0, str, 0, str.length());
148         return bytes;
149     }
150
151     public final @NonNull U uuidFor(final @NonNull UUID uuid) {
152         return uuidFactory.newInstance(uuid.toString());
153     }
154
155     protected abstract String getValue(M macAddress);
156
157     protected abstract String getPhysValue(P physAddress);
158
159     protected abstract String getHexValue(H hexString);
160
161     protected abstract String getQuadValue(Q dottedQuad);
162
163     static byte hexValue(final char ch) {
164         byte value;
165         try {
166             // Performance optimization: access the array and rely on the VM for catching
167             // illegal access (which boils down to illegal character, which should never happen.
168             value = HEX_VALUES[ch];
169         } catch (IndexOutOfBoundsException e) {
170             value = -1;
171         }
172
173         if (value < 0) {
174             throw new IllegalArgumentException("Invalid character '" + ch + "' encountered");
175         }
176
177         return value;
178     }
179
180     /**
181      * Make sure an array of characters does not include capital letters. This method assumes input conforms to
182      * MAC address format, e.g. it is composed of 6 groups of hexadecimal digits separated by colons. Behavior is
183      * undefined if the input does not meet this criteria.
184      *
185      * @param chars Input characters, may not be null
186      * @return True if the array has been modified
187      * @throws NullPointerException if input is null
188      */
189     private static boolean ensureLowerCase(final char @NonNull[] chars) {
190         boolean ret = false;
191
192         for (int i = 0; i < chars.length; ++i) {
193             final char c = chars[i];
194             if (c >= 'A' && c <= 'F') {
195                 chars[i] = Character.toLowerCase(c);
196                 ret = true;
197             }
198         }
199
200         return ret;
201     }
202
203     /**
204      * Convert an array of 6 bytes into canonical MAC address representation, that is 6 groups of two hexadecimal
205      * lower-case digits each, separated by colons.
206      *
207      * @param bytes Input bytes, may not be null
208      * @param charHint Hint at how many characters are needed
209      * @return Canonical MAC address string
210      * @throws NullPointerException if input is null
211      * @throws IllegalArgumentException if length of input is not 6 bytes
212      */
213     private static @NonNull String bytesToString(final byte @NonNull[] bytes, final int charHint) {
214         final StringBuilder sb = new StringBuilder(charHint);
215         appendHexByte(sb, bytes[0]);
216         for (int i = 1; i < bytes.length; ++i) {
217             appendHexByte(sb.append(':'), bytes[i]);
218         }
219
220         return sb.toString();
221     }
222
223     private static void appendHexByte(final StringBuilder sb, final byte byteVal) {
224         final int intVal = Byte.toUnsignedInt(byteVal);
225         sb.append(HEX_CHARS[intVal >>> 4]).append(HEX_CHARS[intVal & 15]);
226     }
227
228     private static byte @NonNull[] stringToBytes(final String str, final int length) {
229         final byte[] ret = new byte[length];
230         for (int i = 0, base = 0; i < length; ++i, base += 3) {
231             ret[i] = (byte) (hexValue(str.charAt(base)) << 4 | hexValue(str.charAt(base + 1)));
232         }
233         return ret;
234     }
235 }