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