BUG-2825: move code around and improve hex lookup
[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 com.google.common.annotations.Beta;
11 import com.google.common.base.Preconditions;
12 import java.util.Arrays;
13 import javax.annotation.Nonnull;
14 import org.opendaylight.yangtools.yang.binding.util.StringValueObjectFactory;
15
16 /**
17  * Abstract utility class for dealing with MAC addresses as defined in the ietf-yang-types model. This class is
18  * used by revision-specific classes.
19  */
20 @Beta
21 public abstract class AbstractIetfYangUtil<T> {
22     private static final int MAC_BYTE_LENGTH = 6;
23     private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
24     private static final byte[] HEX_VALUES;
25     static {
26         final byte[] b = new byte['f' + 1];
27         Arrays.fill(b, (byte)-1);
28
29         for (char c = '0'; c <= '9'; ++c) {
30             b[c] = (byte)(c - '0');
31         }
32         for (char c = 'A'; c <= 'F'; ++c) {
33             b[c] = (byte)(c - 'A' + 10);
34         }
35         for (char c = 'a'; c <= 'f'; ++c) {
36             b[c] = (byte)(c - 'a' + 10);
37         }
38
39         HEX_VALUES = b;
40     }
41
42     private final StringValueObjectFactory<T> factory;
43
44     protected AbstractIetfYangUtil(final Class<T> clazz) {
45         this.factory = StringValueObjectFactory.create(clazz, "00:00:00:00:00:00");
46     }
47
48     private static final void appendHexByte(final StringBuilder sb, final byte b) {
49         final int v = Byte.toUnsignedInt(b);
50         sb.append(HEX_CHARS[v >>> 4]);
51         sb.append(HEX_CHARS[v &  15]);
52     }
53
54     /**
55      * Make sure an array of characters does not include capital letters. This method assumes input conforms to
56      * MAC address format, e.g. it is composed of 6 groups of hexadecimal digits separated by colons. Behavior is
57      * undefined if the input does not meet this criteria.
58      *
59      * @param chars Input characters, may not be null
60      * @return True if the array has been modified
61      * @throws NullPointerException if input is null
62      */
63     private static boolean ensureLowerCase(@Nonnull final char[] chars) {
64         boolean ret = false;
65
66         for (int i = 0; i < chars.length; ++i) {
67             final char c = chars[i];
68             if (c >= 'A' && c <= 'F') {
69                 chars[i] = Character.toLowerCase(c);
70                 ret = true;
71             }
72         }
73
74         return ret;
75     }
76
77     /**
78      * Convert an array of 6 bytes into canonical MAC address representation, that is 6 groups of two hexadecimal
79      * lower-case digits each, separated by colons.
80      *
81      * @param bytes Input bytes, may not be null
82      * @return Canonical MAC address string
83      * @throws NullPointerException if input is null
84      * @throws IllegalArgumentException if length of input is not 6 bytes
85      */
86     @Nonnull private static String bytesToString(@Nonnull final byte[] bytes) {
87         Preconditions.checkArgument(bytes.length == MAC_BYTE_LENGTH, "MAC address should have 6 bytes, not %s",
88                 bytes.length);
89
90         final StringBuilder sb = new StringBuilder(17);
91         appendHexByte(sb, bytes[0]);
92         for (int i = 1; i < MAC_BYTE_LENGTH; ++i) {
93             sb.append(':');
94             appendHexByte(sb, bytes[i]);
95         }
96
97         return sb.toString();
98     }
99
100     /**
101      * Convert the value of a MacAddress into the canonical representation.
102      *
103      * @param macAddress Input MAC address
104      * @return A MacAddress containing the canonical representation.
105      * @throws NullPointerException if macAddress is null
106      */
107     @Nonnull public final T canonizeMacAddress(@Nonnull final T macAddress) {
108         final char[] input = getValue(macAddress).toCharArray();
109         if (ensureLowerCase(input)) {
110             return factory.newInstance(input.toString());
111         } else {
112             return macAddress;
113         }
114     }
115
116     /**
117      * Create a MacAddress object holding the canonical representation of the 6 bytes
118      * passed in as argument.
119      * @param bytes 6-byte input array
120      * @return MacAddress with canonical string derived from input bytes
121      * @throws NullPointerException if bytes is null
122      * @throws IllegalArgumentException if length of input is not 6 bytes
123      */
124     @Nonnull public final T macAddressFor(@Nonnull final byte[] bytes) {
125         return factory.newInstance(bytesToString(bytes));
126     }
127
128     static byte hexValue(final char c) {
129         byte v;
130         try {
131             // Performance optimization: access the array and rely on the VM for catching
132             // illegal access (which boils down to illegal character, which should never happen.
133             v = HEX_VALUES[c];
134         } catch (IndexOutOfBoundsException e) {
135             v = -1;
136         }
137
138         if (v < 0) {
139             throw new IllegalArgumentException("Invalid character '" + c + "' encountered");
140         }
141
142         return v;
143     }
144
145     @Nonnull public final byte[] bytesFor(@Nonnull final T macAddress) {
146         final String mac = getValue(macAddress);
147         final byte[] ret = new byte[MAC_BYTE_LENGTH];
148
149         for (int i = 0, base = 0; i < MAC_BYTE_LENGTH; ++i, base += 3) {
150             ret[i] = (byte) ((hexValue(mac.charAt(base)) << 4) | hexValue(mac.charAt(base + 1)));
151         }
152
153         return ret;
154     }
155
156     protected abstract String getValue(T macAddress);
157 }