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
index dc1d8bc73a748e89cc520724cdda61ba9fe01a70..5f785b1f15dc941ed166b9d991c1c79b5a0da23c 100644 (file)
  */
 package org.opendaylight.mdsal.model.ietf.util;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 import com.google.common.annotations.Beta;
-import com.google.common.base.Preconditions;
-import java.util.Arrays;
-import javax.annotation.Nonnull;
-import org.opendaylight.yangtools.yang.binding.util.StringValueObjectFactory;
+import java.util.HexFormat;
+import java.util.UUID;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.spec.reflect.StringValueObjectFactory;
 
 /**
  * Abstract utility class for dealing with MAC addresses as defined in the ietf-yang-types model. This class is
  * used by revision-specific classes.
+ *
+ * @param <M> mac-address type
+ * @param <P> phys-address type
  */
 @Beta
-public abstract class AbstractIetfYangUtil<T> {
+public abstract class AbstractIetfYangUtil<M, P, H, Q, U> {
     private static final int MAC_BYTE_LENGTH = 6;
-    private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
-    private static final byte[] HEX_VALUES;
-    static {
-        final byte[] b = new byte['f' + 1];
-        Arrays.fill(b, (byte)-1);
-
-        for (char c = '0'; c <= '9'; ++c) {
-            b[c] = (byte)(c - '0');
-        }
-        for (char c = 'A'; c <= 'F'; ++c) {
-            b[c] = (byte)(c - 'A' + 10);
-        }
-        for (char c = 'a'; c <= 'f'; ++c) {
-            b[c] = (byte)(c - 'a' + 10);
-        }
+    private static final HexFormat COLON_HEXFORMAT = HexFormat.ofDelimiter(":");
+    private static final byte @NonNull[] EMPTY_BYTES = new byte[0];
+
+    private final StringValueObjectFactory<M> macFactory;
+    private final StringValueObjectFactory<P> physFactory;
+    private final StringValueObjectFactory<H> hexFactory;
+    private final StringValueObjectFactory<Q> quadFactory;
+    private final StringValueObjectFactory<U> uuidFactory;
+
+    protected AbstractIetfYangUtil(final Class<M> macClass, final Class<P> physClass, final Class<H> hexClass,
+            final Class<Q> quadClass, final Class<U> uuidClass) {
+        this.macFactory = StringValueObjectFactory.create(macClass, "00:00:00:00:00:00");
+        this.physFactory = StringValueObjectFactory.create(physClass, "00:00");
+        this.hexFactory = StringValueObjectFactory.create(hexClass, "00");
+        this.quadFactory = StringValueObjectFactory.create(quadClass, "0.0.0.0");
+        this.uuidFactory = StringValueObjectFactory.create(uuidClass, "f81d4fae-7dec-11d0-a765-00a0c91e6bf6");
+    }
 
-        HEX_VALUES = b;
+    /**
+     * Convert the value of a MacAddress into the canonical representation.
+     *
+     * @param macAddress Input MAC address
+     * @return A MacAddress containing the canonical representation.
+     * @throws NullPointerException if macAddress is null
+     */
+    public final @NonNull M canonizeMacAddress(final @NonNull M macAddress) {
+        final char[] input = getValue(macAddress).toCharArray();
+        return ensureLowerCase(input) ? macFactory.newInstance(String.valueOf(input)) : macAddress;
     }
 
-    private final StringValueObjectFactory<T> factory;
+    /**
+     * Create a MacAddress object holding the canonical representation of the 6 bytes
+     * passed in as argument.
+     * @param bytes 6-byte input array
+     * @return MacAddress with canonical string derived from input bytes
+     * @throws NullPointerException if bytes is null
+     * @throws IllegalArgumentException if length of input is not 6 bytes
+     */
+    public final @NonNull M macAddressFor(final byte @NonNull[] bytes) {
+        checkArgument(bytes.length == MAC_BYTE_LENGTH, "MAC address should have 6 bytes, not %s", bytes.length);
+        return macFactory.newInstance(COLON_HEXFORMAT.formatHex(bytes));
+    }
 
-    protected AbstractIetfYangUtil(final Class<T> clazz) {
-        this.factory = StringValueObjectFactory.create(clazz, "00:00:00:00:00:00");
+    public final byte @NonNull[] macAddressBytes(final @NonNull M macAddress) {
+        return stringToBytes(getValue(macAddress), MAC_BYTE_LENGTH);
     }
 
-    private static final void appendHexByte(final StringBuilder sb, final byte b) {
-        final int v = Byte.toUnsignedInt(b);
-        sb.append(HEX_CHARS[v >>> 4]);
-        sb.append(HEX_CHARS[v &  15]);
+    /**
+     * Convert the value of a PhysAddress into the canonical representation.
+     *
+     * @param physAddress Input MAC address
+     * @return A PhysAddress containing the canonical representation.
+     * @throws NullPointerException if physAddress is null
+     */
+    public final @NonNull P canonizePhysAddress(final @NonNull P physAddress) {
+        final char[] input = getPhysValue(physAddress).toCharArray();
+        return ensureLowerCase(input) ? physFactory.newInstance(String.valueOf(input)) : physAddress;
     }
 
+    /**
+     * Create a PhysAddress object holding the canonical representation of the bytes passed in as argument.
+     *
+     * @param bytes input array
+     * @return PhysAddress with canonical string derived from input bytes
+     * @throws NullPointerException if bytes is null
+     * @throws IllegalArgumentException if length of input is not at least 1 byte
+     */
+    public final @NonNull P physAddressFor(final byte @NonNull[] bytes) {
+        checkArgument(bytes.length > 0, "Physical address should have at least one byte");
+        return physFactory.newInstance(COLON_HEXFORMAT.formatHex(bytes));
+    }
+
+    public final byte @NonNull[] physAddressBytes(final @NonNull P physAddress) {
+        final String str = getPhysValue(physAddress);
+        return str.isEmpty() ? EMPTY_BYTES : stringToBytes(str, str.length() / 3 + 1);
+    }
+
+    public final @NonNull H hexStringFor(final byte @NonNull[] bytes) {
+        checkArgument(bytes.length > 0, "Hex string should have at least one byte");
+        return hexFactory.newInstance(COLON_HEXFORMAT.formatHex(bytes));
+    }
+
+    public final byte @NonNull[] hexStringBytes(final @NonNull H hexString) {
+        final String str = getHexValue(hexString);
+        return stringToBytes(str, str.length() / 3 + 1);
+    }
+
+    public final @NonNull Q dottedQuadFor(final byte @NonNull[] bytes) {
+        checkArgument(bytes.length == 4, "Dotted-quad should have 4 bytes");
+        return quadFactory.newInstance(AbstractIetfInetUtil.addressStringV4(bytes));
+    }
+
+    public final @NonNull Q dottedQuadFor(final int bits) {
+        return quadFactory.newInstance(Ipv4Utils.addressString(bits));
+    }
+
+    public final int dottedQuadBits(final @NonNull Q dottedQuad) {
+        final String str = getQuadValue(dottedQuad);
+        return Ipv4Utils.addressBits(str, str.length());
+    }
+
+    public final byte @NonNull[] dottedQuadBytes(final @NonNull Q dottedQuad) {
+        final String str = getQuadValue(dottedQuad);
+        return Ipv4Utils.addressBytes(str, str.length());
+    }
+
+    public final @NonNull U uuidFor(final @NonNull UUID uuid) {
+        return uuidFactory.newInstance(uuid.toString());
+    }
+
+    protected abstract String getValue(M macAddress);
+
+    protected abstract String getPhysValue(P physAddress);
+
+    protected abstract String getHexValue(H hexString);
+
+    protected abstract String getQuadValue(Q dottedQuad);
+
     /**
      * Make sure an array of characters does not include capital letters. This method assumes input conforms to
      * MAC address format, e.g. it is composed of 6 groups of hexadecimal digits separated by colons. Behavior is
@@ -60,13 +152,14 @@ public abstract class AbstractIetfYangUtil<T> {
      * @return True if the array has been modified
      * @throws NullPointerException if input is null
      */
-    private static boolean ensureLowerCase(@Nonnull final char[] chars) {
+    private static boolean ensureLowerCase(final char @NonNull[] chars) {
         boolean ret = false;
 
         for (int i = 0; i < chars.length; ++i) {
             final char c = chars[i];
             if (c >= 'A' && c <= 'F') {
-                chars[i] = Character.toLowerCase(c);
+                // Weird notation to ensure constant folding to '(char) (c + 32)', a well-known property of ASCII
+                chars[i] = (char) (c + ('a' - 'A'));
                 ret = true;
             }
         }
@@ -74,84 +167,12 @@ public abstract class AbstractIetfYangUtil<T> {
         return ret;
     }
 
-    /**
-     * Convert an array of 6 bytes into canonical MAC address representation, that is 6 groups of two hexadecimal
-     * lower-case digits each, separated by colons.
-     *
-     * @param bytes Input bytes, may not be null
-     * @return Canonical MAC address string
-     * @throws NullPointerException if input is null
-     * @throws IllegalArgumentException if length of input is not 6 bytes
-     */
-    @Nonnull private static String bytesToString(@Nonnull final byte[] bytes) {
-        Preconditions.checkArgument(bytes.length == MAC_BYTE_LENGTH, "MAC address should have 6 bytes, not %s",
-                bytes.length);
-
-        final StringBuilder sb = new StringBuilder(17);
-        appendHexByte(sb, bytes[0]);
-        for (int i = 1; i < MAC_BYTE_LENGTH; ++i) {
-            sb.append(':');
-            appendHexByte(sb, bytes[i]);
-        }
-
-        return sb.toString();
-    }
-
-    /**
-     * Convert the value of a MacAddress into the canonical representation.
-     *
-     * @param macAddress Input MAC address
-     * @return A MacAddress containing the canonical representation.
-     * @throws NullPointerException if macAddress is null
-     */
-    @Nonnull public final T canonizeMacAddress(@Nonnull final T macAddress) {
-        final char[] input = getValue(macAddress).toCharArray();
-        if (ensureLowerCase(input)) {
-            return factory.newInstance(input.toString());
-        } else {
-            return macAddress;
-        }
-    }
-
-    /**
-     * Create a MacAddress object holding the canonical representation of the 6 bytes
-     * passed in as argument.
-     * @param bytes 6-byte input array
-     * @return MacAddress with canonical string derived from input bytes
-     * @throws NullPointerException if bytes is null
-     * @throws IllegalArgumentException if length of input is not 6 bytes
-     */
-    @Nonnull public final T macAddressFor(@Nonnull final byte[] bytes) {
-        return factory.newInstance(bytesToString(bytes));
-    }
-
-    static byte hexValue(final char c) {
-        byte v;
-        try {
-            // Performance optimization: access the array and rely on the VM for catching
-            // illegal access (which boils down to illegal character, which should never happen.
-            v = HEX_VALUES[c];
-        } catch (IndexOutOfBoundsException e) {
-            v = -1;
-        }
-
-        if (v < 0) {
-            throw new IllegalArgumentException("Invalid character '" + c + "' encountered");
-        }
-
-        return v;
-    }
-
-    @Nonnull public final byte[] bytesFor(@Nonnull final T macAddress) {
-        final String mac = getValue(macAddress);
-        final byte[] ret = new byte[MAC_BYTE_LENGTH];
-
-        for (int i = 0, base = 0; i < MAC_BYTE_LENGTH; ++i, base += 3) {
-            ret[i] = (byte) ((hexValue(mac.charAt(base)) << 4) | hexValue(mac.charAt(base + 1)));
+    private static byte @NonNull[] stringToBytes(final String str, final int length) {
+        final byte[] ret = new byte[length];
+        for (int i = 0, base = 0; i < length; ++i, base += 3) {
+            ret[i] = (byte) ((HexFormat.fromHexDigit(str.charAt(base)) << 4)
+                + HexFormat.fromHexDigit(str.charAt(base + 1)));
         }
-
         return ret;
     }
-
-    protected abstract String getValue(T macAddress);
 }