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