Use HexFormat instead of home-grown formatting
[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.HexFormat;
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 HexFormat COLON_HEXFORMAT = HexFormat.ofDelimiter(":");
29     private static final byte @NonNull[] EMPTY_BYTES = new byte[0];
30
31     private final StringValueObjectFactory<M> macFactory;
32     private final StringValueObjectFactory<P> physFactory;
33     private final StringValueObjectFactory<H> hexFactory;
34     private final StringValueObjectFactory<Q> quadFactory;
35     private final StringValueObjectFactory<U> uuidFactory;
36
37     protected AbstractIetfYangUtil(final Class<M> macClass, final Class<P> physClass, final Class<H> hexClass,
38             final Class<Q> quadClass, final Class<U> uuidClass) {
39         this.macFactory = StringValueObjectFactory.create(macClass, "00:00:00:00:00:00");
40         this.physFactory = StringValueObjectFactory.create(physClass, "00:00");
41         this.hexFactory = StringValueObjectFactory.create(hexClass, "00");
42         this.quadFactory = StringValueObjectFactory.create(quadClass, "0.0.0.0");
43         this.uuidFactory = StringValueObjectFactory.create(uuidClass, "f81d4fae-7dec-11d0-a765-00a0c91e6bf6");
44     }
45
46     /**
47      * Convert the value of a MacAddress into the canonical representation.
48      *
49      * @param macAddress Input MAC address
50      * @return A MacAddress containing the canonical representation.
51      * @throws NullPointerException if macAddress is null
52      */
53     public final @NonNull M canonizeMacAddress(final @NonNull M macAddress) {
54         final char[] input = getValue(macAddress).toCharArray();
55         return ensureLowerCase(input) ? macFactory.newInstance(String.valueOf(input)) : macAddress;
56     }
57
58     /**
59      * Create a MacAddress object holding the canonical representation of the 6 bytes
60      * passed in as argument.
61      * @param bytes 6-byte input array
62      * @return MacAddress with canonical string derived from input bytes
63      * @throws NullPointerException if bytes is null
64      * @throws IllegalArgumentException if length of input is not 6 bytes
65      */
66     public final @NonNull M macAddressFor(final byte @NonNull[] bytes) {
67         checkArgument(bytes.length == MAC_BYTE_LENGTH, "MAC address should have 6 bytes, not %s", bytes.length);
68         return macFactory.newInstance(COLON_HEXFORMAT.formatHex(bytes));
69     }
70
71     public final byte @NonNull[] macAddressBytes(final @NonNull M macAddress) {
72         return stringToBytes(getValue(macAddress), MAC_BYTE_LENGTH);
73     }
74
75     /**
76      * Convert the value of a PhysAddress into the canonical representation.
77      *
78      * @param physAddress Input MAC address
79      * @return A PhysAddress containing the canonical representation.
80      * @throws NullPointerException if physAddress is null
81      */
82     public final @NonNull P canonizePhysAddress(final @NonNull P physAddress) {
83         final char[] input = getPhysValue(physAddress).toCharArray();
84         return ensureLowerCase(input) ? physFactory.newInstance(String.valueOf(input)) : physAddress;
85     }
86
87     /**
88      * Create a PhysAddress object holding the canonical representation of the bytes passed in as argument.
89      *
90      * @param bytes input array
91      * @return PhysAddress with canonical string derived from input bytes
92      * @throws NullPointerException if bytes is null
93      * @throws IllegalArgumentException if length of input is not at least 1 byte
94      */
95     public final @NonNull P physAddressFor(final byte @NonNull[] bytes) {
96         checkArgument(bytes.length > 0, "Physical address should have at least one byte");
97         return physFactory.newInstance(COLON_HEXFORMAT.formatHex(bytes));
98     }
99
100     public final byte @NonNull[] physAddressBytes(final @NonNull P physAddress) {
101         final String str = getPhysValue(physAddress);
102         return str.isEmpty() ? EMPTY_BYTES : stringToBytes(str, str.length() / 3 + 1);
103     }
104
105     public final @NonNull H hexStringFor(final byte @NonNull[] bytes) {
106         checkArgument(bytes.length > 0, "Hex string should have at least one byte");
107         return hexFactory.newInstance(COLON_HEXFORMAT.formatHex(bytes));
108     }
109
110     public final byte @NonNull[] hexStringBytes(final @NonNull H hexString) {
111         final String str = getHexValue(hexString);
112         return stringToBytes(str, str.length() / 3 + 1);
113     }
114
115     public final @NonNull Q dottedQuadFor(final byte @NonNull[] bytes) {
116         checkArgument(bytes.length == 4, "Dotted-quad should have 4 bytes");
117         return quadFactory.newInstance(AbstractIetfInetUtil.addressStringV4(bytes));
118     }
119
120     public final @NonNull Q dottedQuadFor(final int bits) {
121         return quadFactory.newInstance(Ipv4Utils.addressString(bits));
122     }
123
124     public final int dottedQuadBits(final @NonNull Q dottedQuad) {
125         final String str = getQuadValue(dottedQuad);
126         return Ipv4Utils.addressBits(str, str.length());
127     }
128
129     public final byte @NonNull[] dottedQuadBytes(final @NonNull Q dottedQuad) {
130         final String str = getQuadValue(dottedQuad);
131         return Ipv4Utils.addressBytes(str, str.length());
132     }
133
134     public final @NonNull U uuidFor(final @NonNull UUID uuid) {
135         return uuidFactory.newInstance(uuid.toString());
136     }
137
138     protected abstract String getValue(M macAddress);
139
140     protected abstract String getPhysValue(P physAddress);
141
142     protected abstract String getHexValue(H hexString);
143
144     protected abstract String getQuadValue(Q dottedQuad);
145
146     /**
147      * Make sure an array of characters does not include capital letters. This method assumes input conforms to
148      * MAC address format, e.g. it is composed of 6 groups of hexadecimal digits separated by colons. Behavior is
149      * undefined if the input does not meet this criteria.
150      *
151      * @param chars Input characters, may not be null
152      * @return True if the array has been modified
153      * @throws NullPointerException if input is null
154      */
155     private static boolean ensureLowerCase(final char @NonNull[] chars) {
156         boolean ret = false;
157
158         for (int i = 0; i < chars.length; ++i) {
159             final char c = chars[i];
160             if (c >= 'A' && c <= 'F') {
161                 // Weird notation to ensure constant folding to '(char) (c + 32)', a well-known property of ASCII
162                 chars[i] = (char) (c + ('a' - 'A'));
163                 ret = true;
164             }
165         }
166
167         return ret;
168     }
169
170     private static byte @NonNull[] stringToBytes(final String str, final int length) {
171         final byte[] ret = new byte[length];
172         for (int i = 0, base = 0; i < length; ++i, base += 3) {
173             ret[i] = (byte) ((HexFormat.fromHexDigit(str.charAt(base)) << 4)
174                 + HexFormat.fromHexDigit(str.charAt(base + 1)));
175         }
176         return ret;
177     }
178 }