Fix ietf-type-util checkstyle
[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[] HEX_VALUES;
29
30     static {
31         final byte[] b = new byte['f' + 1];
32         Arrays.fill(b, (byte)-1);
33
34         for (char c = '0'; c <= '9'; ++c) {
35             b[c] = (byte)(c - '0');
36         }
37         for (char c = 'A'; c <= 'F'; ++c) {
38             b[c] = (byte)(c - 'A' + 10);
39         }
40         for (char c = 'a'; c <= 'f'; ++c) {
41             b[c] = (byte)(c - 'a' + 10);
42         }
43
44         HEX_VALUES = b;
45     }
46
47     private final StringValueObjectFactory<M> macFactory;
48     private final StringValueObjectFactory<P> physFactory;
49
50     protected AbstractIetfYangUtil(final Class<M> macClass, final Class<P> physClass) {
51         this.macFactory = StringValueObjectFactory.create(macClass, "00:00:00:00:00:00");
52         this.physFactory = StringValueObjectFactory.create(physClass, "00:00");
53     }
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     /**
83      * Convert the value of a PhysAddress into the canonical representation.
84      *
85      * @param physAddress Input MAC address
86      * @return A PhysAddress containing the canonical representation.
87      * @throws NullPointerException if physAddress is null
88      */
89     public final @NonNull P canonizePhysAddress(final @NonNull P physAddress) {
90         final char[] input = getPhysValue(physAddress).toCharArray();
91         return ensureLowerCase(input) ? physFactory.newInstance(String.valueOf(input)) : physAddress;
92     }
93
94     /**
95      * Create a PhysAddress object holding the canonical representation of the bytes passed in as argument.
96      *
97      * @param bytes input array
98      * @return PhysAddress with canonical string derived from input bytes
99      * @throws NullPointerException if bytes is null
100      * @throws IllegalArgumentException if length of input is not at least 1 byte
101      */
102     public final @NonNull P physAddressFor(final byte @NonNull[] bytes) {
103         checkArgument(bytes.length > 0, "Physical address should have at least one byte");
104         return physFactory.newInstance(bytesToString(bytes, (bytes.length + 1) / 3));
105     }
106
107     public final byte @NonNull[] bytesFor(final @NonNull M macAddress) {
108         final String mac = getValue(macAddress);
109         final byte[] ret = new byte[MAC_BYTE_LENGTH];
110
111         for (int i = 0, base = 0; i < MAC_BYTE_LENGTH; ++i, base += 3) {
112             ret[i] = (byte) (hexValue(mac.charAt(base)) << 4 | hexValue(mac.charAt(base + 1)));
113         }
114
115         return ret;
116     }
117
118     protected abstract String getValue(M macAddress);
119
120     protected abstract String getPhysValue(P physAddress);
121
122     static byte hexValue(final char ch) {
123         byte value;
124         try {
125             // Performance optimization: access the array and rely on the VM for catching
126             // illegal access (which boils down to illegal character, which should never happen.
127             value = HEX_VALUES[ch];
128         } catch (IndexOutOfBoundsException e) {
129             value = -1;
130         }
131
132         if (value < 0) {
133             throw new IllegalArgumentException("Invalid character '" + ch + "' encountered");
134         }
135
136         return value;
137     }
138
139     /**
140      * Make sure an array of characters does not include capital letters. This method assumes input conforms to
141      * MAC address format, e.g. it is composed of 6 groups of hexadecimal digits separated by colons. Behavior is
142      * undefined if the input does not meet this criteria.
143      *
144      * @param chars Input characters, may not be null
145      * @return True if the array has been modified
146      * @throws NullPointerException if input is null
147      */
148     private static boolean ensureLowerCase(final char @NonNull[] chars) {
149         boolean ret = false;
150
151         for (int i = 0; i < chars.length; ++i) {
152             final char c = chars[i];
153             if (c >= 'A' && c <= 'F') {
154                 chars[i] = Character.toLowerCase(c);
155                 ret = true;
156             }
157         }
158
159         return ret;
160     }
161
162     /**
163      * Convert an array of 6 bytes into canonical MAC address representation, that is 6 groups of two hexadecimal
164      * lower-case digits each, separated by colons.
165      *
166      * @param bytes Input bytes, may not be null
167      * @param charHint Hint at how many characters are needed
168      * @return Canonical MAC address string
169      * @throws NullPointerException if input is null
170      * @throws IllegalArgumentException if length of input is not 6 bytes
171      */
172     private static @NonNull String bytesToString(final byte @NonNull[] bytes, final int charHint) {
173         final StringBuilder sb = new StringBuilder(charHint);
174         appendHexByte(sb, bytes[0]);
175         for (int i = 1; i < bytes.length; ++i) {
176             appendHexByte(sb.append(':'), bytes[i]);
177         }
178
179         return sb.toString();
180     }
181
182     private static void appendHexByte(final StringBuilder sb, final byte byteVal) {
183         final int intVal = Byte.toUnsignedInt(byteVal);
184         sb.append(HEX_CHARS[intVal >>> 4]).append(HEX_CHARS[intVal & 15]);
185     }
186 }