From: Robert Varga Date: Thu, 18 Feb 2016 00:49:05 +0000 (+0100) Subject: BUG-2825: add utility methods for instantiating DTOs X-Git-Tag: release/boron~206 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;ds=sidebyside;h=4ba08793b4fe9fbc958270de742241e5f093d1b4;p=mdsal.git BUG-2825: add utility methods for instantiating DTOs Change-Id: I9a98c55b850bf13695c8f581f8acee81705d570b Signed-off-by: Robert Varga --- diff --git a/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/StringValueObjectFactory.java b/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/StringValueObjectFactory.java new file mode 100644 index 0000000000..d15d580803 --- /dev/null +++ b/binding/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/StringValueObjectFactory.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2016 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.binding.util; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility class for instantiating value-type generated objects with String being the base type. Unlike the normal + * constructor, instances of this class bypass string validation. + * + * THE USE OF THIS CLASS IS DANGEROUS AND SHOULD ONLY BE USED TO IMPLEMENT WELL-AUDITED AND CORRECT UTILITY METHODS + * SHIPPED WITH MODELS TO PROVIDE INSTANTIATION FROM TYPES DIFFERENT THAN STRING. + * + * APPLICATION CODE MUST NOT USE THIS CLASS DIRECTLY. VIOLATING THIS CONSTRAINT HAS SECURITY AND CORRECTNESS + * IMPLICATIONS ON EVERY USER INTERACTING WITH THE RESULTING OBJECTS. + * + * @param Resulting object type + */ +@Beta +public final class StringValueObjectFactory { + private static final MethodType CONSTRUCTOR_METHOD_TYPE = MethodType.methodType(Object.class, Object.class); + private static final MethodType SETTER_METHOD_TYPE = MethodType.methodType(void.class, Object.class, String.class); + private static final Logger LOG = LoggerFactory.getLogger(StringValueObjectFactory.class); + private static final Lookup LOOKUP = MethodHandles.lookup(); + + private final MethodHandle constructor; + private final MethodHandle setter; + + private StringValueObjectFactory(final MethodHandle constructor, final MethodHandle setter) { + this.constructor = Preconditions.checkNotNull(constructor); + this.setter = Preconditions.checkNotNull(setter); + } + + public static StringValueObjectFactory create(final Class clazz, final String templateString) { + final Constructor stringConstructor; + try { + stringConstructor = clazz.getConstructor(String.class); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(String.format("%s does not have a String constructor", clazz), e); + } + + final T template; + try { + template = stringConstructor.newInstance(templateString); + } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) { + throw new IllegalArgumentException(String.format("Failed to instantiate template %s for '%s'", clazz, + templateString), e); + } + + final Constructor copyConstructor; + try { + copyConstructor = clazz.getConstructor(clazz); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(String.format("%s does not have a copy constructor", clazz), e); + } + + final Field f; + try { + f = clazz.getDeclaredField("_value"); + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException(String.format("%s does not have required internal field", clazz), e); + } + f.setAccessible(true); + + final StringValueObjectFactory ret; + try { + ret = new StringValueObjectFactory<>( + LOOKUP.unreflectConstructor(copyConstructor).asType(CONSTRUCTOR_METHOD_TYPE).bindTo(template), + LOOKUP.unreflectSetter(f).asType(SETTER_METHOD_TYPE)); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Failed to instantiate method handles", e); + } + + // Let us be very defensive and scream loudly if the invocation does not come from the same package. This + // is far from perfect, but better than nothing. + final Throwable t = new Throwable("Invocation stack"); + t.fillInStackTrace(); + if (matchesPackage(clazz.getPackage(), t.getStackTrace())) { + LOG.info("Instantiated factory for {}", clazz); + } else { + LOG.warn("Instantiated factory for {} outside its package", clazz, t); + } + + return ret; + } + + private static boolean matchesPackage(final Package pkg, final StackTraceElement[] stackTrace) { + for (StackTraceElement e : stackTrace) { + if (pkg.equals(e.getClass().getPackage())) { + return true; + } + } + + return false; + } + + public T newInstance(final String string) { + Preconditions.checkNotNull(string, "Argument may not be null"); + + try { + final T ret = (T) constructor.invokeExact(); + setter.invokeExact(ret, string); + LOG.trace("Instantiated new object {} value {}", ret.getClass(), string); + return ret; + } catch (Throwable e) { + throw Throwables.propagate(e); + } + } +} diff --git a/model/artifacts/pom.xml b/model/artifacts/pom.xml index b882165b64..fc95fcb187 100644 --- a/model/artifacts/pom.xml +++ b/model/artifacts/pom.xml @@ -122,6 +122,11 @@ general-entity 0.9.0-SNAPSHOT + + org.opendaylight.mdsal.model + ietf-type-util + 1.0.0-SNAPSHOT + diff --git a/model/features/pom.xml b/model/features/pom.xml index 084d35bd3e..3f71f64097 100644 --- a/model/features/pom.xml +++ b/model/features/pom.xml @@ -123,6 +123,10 @@ org.opendaylight.mdsal.model ietf-inet-types-2013-07-15 + + org.opendaylight.mdsal.model + ietf-type-util + @@ -16,6 +17,17 @@ bundle + + + org.opendaylight.mdsal.model + ietf-type-util + + + junit + junit + test + + + ${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/ + + + + opendaylight-site + ${nexus.site.url}/${project.artifactId}/ + + + + diff --git a/model/ietf/ietf-type-util/src/main/java/org/opendaylight/mdsal/model/ietf/util/AbstractIetfInetUtil.java b/model/ietf/ietf-type-util/src/main/java/org/opendaylight/mdsal/model/ietf/util/AbstractIetfInetUtil.java new file mode 100644 index 0000000000..7781d6fc75 --- /dev/null +++ b/model/ietf/ietf-type-util/src/main/java/org/opendaylight/mdsal/model/ietf/util/AbstractIetfInetUtil.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2016 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.model.ietf.util; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.util.StringValueObjectFactory; + +/** + * A set of utility methods to efficiently instantiate various ietf-inet-types DTOs. + * + * FIXME: IPv6 addresses are not emitted in canonical format as specified by the model. + */ +@Beta +public abstract class AbstractIetfInetUtil { + private final StringValueObjectFactory address4Factory; + private final StringValueObjectFactory prefix4Factory; + private final StringValueObjectFactory address6Factory; + private final StringValueObjectFactory prefix6Factory; + + protected AbstractIetfInetUtil(final Class addr4Class, final Class prefix4Class, + final Class addr6Class, final Class prefix6Class) { + this.address4Factory = StringValueObjectFactory.create(addr4Class, "0.0.0.0"); + this.prefix4Factory = StringValueObjectFactory.create(prefix4Class, "0.0.0.0/0"); + this.address6Factory = StringValueObjectFactory.create(addr6Class, "::0"); + this.prefix6Factory = StringValueObjectFactory.create(prefix6Class, "::0/0"); + } + + protected abstract A ipv4Address(A4 addr); + protected abstract A ipv6Address(A6 addr); + + @Nonnull public final A ipAddressFor(@Nonnull final byte[] bytes) { + switch (bytes.length) { + case 4: + return ipv4Address(ipv4AddressFor(bytes)); + case 16: + return ipv6Address(ipv6AddressFor(bytes)); + default: + throw new IllegalArgumentException("Invalid array length " + bytes.length); + } + } + + @Nonnull public final A ipAddressFor(@Nonnull final InetAddress addr) { + Preconditions.checkNotNull(addr, "Address must not be null"); + if (addr instanceof Inet4Address) { + return ipv4Address(ipv4AddressFor(addr)); + } else if (addr instanceof Inet6Address) { + return ipv6Address(ipv6AddressFor(addr)); + } else { + throw new IllegalArgumentException("Unhandled address " + addr); + } + } + + /** + * Create an Ipv4Address by interpreting input bytes as an IPv4 address. + * + * @param bytes 4-byte array + * @return An Ipv4Address object + * @throws IllegalArgumentException if bytes has length different from 4 + * @throws NullPointerException if bytes is null + */ + @Nonnull public final A4 ipv4AddressFor(@Nonnull final byte[] bytes) { + return address4Factory.newInstance(addressStringV4(bytes)); + } + + /** + * Create an Ipv4Address by interpreting an {@link Inet4Address}. + * + * @param addr An {@link Inet4Address} + * @return An Ipv4Address object + * @throws IllegalArgumentException if addr is not an {@link Inet4Address} + * @throws NullPointerException if addr is null + */ + @Nonnull public final A4 ipv4AddressFor(@Nonnull final InetAddress addr) { + Preconditions.checkNotNull(addr, "Address must not be null"); + Preconditions.checkArgument(addr instanceof Inet4Address, "Address has to be an Inet4Address"); + return address4Factory.newInstance(addr.getHostAddress()); + } + + /** + * Create a /32 Ipv4Prefix by interpreting input bytes as an IPv4 address. + * + * @param bytes four-byte array + * @return An Ipv4Prefix object + * @throws IllegalArgumentException if bytes has length different from 4 + * @throws NullPointerException if bytes is null + */ + @Nonnull public final P4 ipv4PrefixFor(@Nonnull final byte[] bytes) { + return prefix4Factory.newInstance(prefixStringV4(bytes)); + } + + /** + * Create a Ipv4Prefix by combining the address with a mask. The address + * bytes are interpreted as an address and the specified mask is concatenated to + * it. The address bytes are not masked, hence input address = { 1, 2, 3, 4 } + * and mask=24 will result in 1.2.3.4/24. + * + * @param address Input address as a 4-byte array + * @param mask Prefix mask + * @return An Ipv4Prefix object + * @throws IllegalArgumentException if bytes has length different from 4 or if mask is not in range 0-32 + * @throws NullPointerException if bytes is null + */ + @Nonnull public final P4 ipv4PrefixFor(@Nonnull final byte[] address, final int mask) { + return prefix4Factory.newInstance(prefixStringV4(address, mask)); + } + + /** + * Create a /32 Ipv4Prefix for an {@link Inet4Address} + * + * @param addr An {@link Inet4Address} + * @return An Ipv4Prefix object + * @throws IllegalArgumentException if addr is not an Inet4Address + * @throws NullPointerException if adds is null + */ + @Nonnull public final P4 ipv4PrefixFor(@Nonnull final InetAddress addr) { + Preconditions.checkNotNull(addr, "Address must not be null"); + Preconditions.checkArgument(addr instanceof Inet4Address, "Address has to be an Inet4Address"); + return prefix4Factory.newInstance(addr.getHostAddress() + "/32"); + } + + /** + * Create a Ipv4Prefix by combining the address with a mask. The address bytes are not masked. + * + * @param addr An {@link Inet4Address} + * @param mask Prefix mask + * @return An Ipv4Prefix object + * @throws IllegalArgumentException if addr is not an Inet4Address or if mask is not in range 0-32 + * @throws NullPointerException if addr is null + */ + @Nonnull public final P4 ipv4PrefixFor(@Nonnull final InetAddress addr, final int mask) { + Preconditions.checkNotNull(addr, "Address must not be null"); + Preconditions.checkArgument(addr instanceof Inet4Address, "Address has to be an Inet4Address"); + Preconditions.checkArgument(mask >= 0 && mask <= 32, "Invalid mask %s", mask); + return prefix4Factory.newInstance(addr.getHostAddress() + '/' + mask); + } + + /** + * Create an Ipv6Address by interpreting input bytes as an IPv6 address. + * + * @param bytes 16-byte array + * @return An Ipv6Address object + * @throws IllegalArgumentException if bytes has length different from 16 + * @throws NullPointerException if bytes is null + */ + @Nonnull public final A6 ipv6AddressFor(@Nonnull final byte[] bytes) { + return address6Factory.newInstance(addressStringV6(bytes)); + } + + /** + * Create an Ipv6Address by interpreting an {@link Inet6Address}. + * + * @param addr An {@link Inet6Address} + * @return An Ipv6Address object + * @throws IllegalArgumentException if addr is not an {@link Inet6Address} + * @throws NullPointerException if addr is null + */ + @Nonnull public final A6 ipv6AddressFor(@Nonnull final InetAddress addr) { + Preconditions.checkNotNull(addr, "Address must not be null"); + Preconditions.checkArgument(addr instanceof Inet6Address, "Address has to be an Inet6Address"); + return address6Factory.newInstance(addr.getHostAddress()); + } + + /** + * Create a /128 Ipv6Prefix by interpreting input bytes as an IPv6 address. + * + * @param bytes four-byte array + * @return An Ipv6Prefix object + * @throws IllegalArgumentException if bytes has length different from 16 + * @throws NullPointerException if bytes is null + */ + @Nonnull public final P6 ipv6PrefixFor(@Nonnull final byte[] bytes) { + return prefix6Factory.newInstance(addressStringV6(bytes) + "/128"); + } + + /** + * Create a Ipv6Prefix by combining the address with a mask. The address + * bytes are interpreted as an address and the specified mask is concatenated to + * it. The address bytes are not masked. + * + * @param address Input address as a 4-byte array + * @param mask Prefix mask + * @return An Ipv6Prefix object + * @throws IllegalArgumentException if bytes has length different from 16 or if mask is not in range 0-128 + * @throws NullPointerException if bytes is null + */ + @Nonnull public final P6 ipv6PrefixFor(@Nonnull final byte[] address, final int mask) { + Preconditions.checkArgument(mask >= 0 && mask <= 128, "Invalid mask %s", mask); + return prefix6Factory.newInstance(addressStringV6(address) + '/' + mask); + } + + /** + * Create a /128 Ipv6Prefix by interpreting input bytes as an IPv4 address. + * + * @param addr an {@link Inet6Address} + * @return An Ipv6Prefix object + * @throws IllegalArgumentException if addr is not an Inet6Address or if mask is not in range 0-128 + * @throws NullPointerException if addr is null + */ + @Nonnull public final P6 ipv6PrefixFor(@Nonnull final InetAddress addr) { + return prefix6Factory.newInstance(addr.getHostAddress() + "/128"); + } + + /** + * Create a Ipv6Prefix by combining the address with a mask. The address + * bytes are interpreted as an address and the specified mask is concatenated to + * it. The address bytes are not masked. + * + * @param addr Input address + * @param mask Prefix mask + * @return An Ipv6Prefix object + * @throws IllegalArgumentException if addr is not an Inet6Address or if mask is not in range 0-128 + * @throws NullPointerException if addr is null + */ + @Nonnull public final P6 ipv6PrefixFor(@Nonnull final InetAddress addr, final int mask) { + Preconditions.checkNotNull(addr, "Address must not be null"); + Preconditions.checkArgument(addr instanceof Inet6Address, "Address has to be an Inet6Address"); + Preconditions.checkArgument(mask >= 0 && mask <= 128, "Invalid mask %s", mask); + return prefix6Factory.newInstance(addr.getHostAddress() + '/' + mask); + } + + private static void appendIpv4String(final StringBuilder sb, final byte[] bytes) { + Preconditions.checkArgument(bytes.length == 4, "IPv4 address length is 4 bytes"); + + sb.append(Byte.toUnsignedInt(bytes[0])); + for (int i = 1; i < 4; ++i) { + sb.append('.'); + sb.append(Byte.toUnsignedInt(bytes[i])); + } + } + + private static String addressStringV4(final byte[] bytes) { + final StringBuilder sb = new StringBuilder(15); + appendIpv4String(sb, bytes); + return sb.toString(); + } + + private static String addressStringV6(final byte[] bytes) { + Preconditions.checkArgument(bytes.length == 16, "IPv6 address length is 16 bytes"); + + try { + return Inet6Address.getByAddress(bytes).getHostAddress(); + } catch (UnknownHostException e) { + throw new IllegalArgumentException(String.format("Invalid input %s", bytes), e); + } + } + + private static String prefixStringV4(final byte[] bytes) { + final StringBuilder sb = new StringBuilder(18); + appendIpv4String(sb, bytes); + sb.append("/32"); + return sb.toString(); + } + + private static String prefixStringV4(final byte[] bytes, final int mask) { + Preconditions.checkArgument(mask >= 0 && mask <= 32, "Invalid mask %s", mask); + + final StringBuilder sb = new StringBuilder(18); + appendIpv4String(sb, bytes); + sb.append('/'); + sb.append(mask); + return sb.toString(); + } +} diff --git a/model/ietf/ietf-type-util/src/main/java/org/opendaylight/mdsal/model/ietf/util/AbstractIetfYangUtil.java b/model/ietf/ietf-type-util/src/main/java/org/opendaylight/mdsal/model/ietf/util/AbstractIetfYangUtil.java new file mode 100644 index 0000000000..0c87fd9ccb --- /dev/null +++ b/model/ietf/ietf-type-util/src/main/java/org/opendaylight/mdsal/model/ietf/util/AbstractIetfYangUtil.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2016 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.model.ietf.util; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.util.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. + */ +@Beta +public abstract class AbstractIetfYangUtil { + private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray(); + private final StringValueObjectFactory factory; + + protected AbstractIetfYangUtil(final Class clazz) { + this.factory = StringValueObjectFactory.create(clazz, "00:00:00:00:00:00"); + } + + 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]); + } + + /** + * 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 + * undefined if the input does not meet this criteria. + * + * @param chars Input characters, may not be null + * @return True if the array has been modified + * @throws NullPointerException if input is null + */ + private static boolean ensureLowerCase(@Nonnull final char[] 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); + ret = true; + } + } + + 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 == 6, "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 < bytes.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)); + } + + protected abstract String getValue(T macAddress); +} diff --git a/model/ietf/ietf-yang-types-20130715/pom.xml b/model/ietf/ietf-yang-types-20130715/pom.xml index 587e4a8b81..3b51f564f5 100644 --- a/model/ietf/ietf-yang-types-20130715/pom.xml +++ b/model/ietf/ietf-yang-types-20130715/pom.xml @@ -23,6 +23,12 @@ ${project.artifactId} bundle + + + org.opendaylight.mdsal.model + ietf-type-util + + - ${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/ - - - - opendaylight-site - ${nexus.site.url}/${project.artifactId}/ - - - + + + org.opendaylight.mdsal.model + ietf-type-util + + + + + ${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/ + + + opendaylight-site + ${nexus.site.url}/${project.artifactId}/ + + diff --git a/model/ietf/ietf-yang-types/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/yang/types/rev100924/IetfYangUtil.java b/model/ietf/ietf-yang-types/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/yang/types/rev100924/IetfYangUtil.java new file mode 100644 index 0000000000..f9f21a7c52 --- /dev/null +++ b/model/ietf/ietf-yang-types/src/main/java/org/opendaylight/yang/gen/v1/urn/ietf/params/xml/ns/yang/ietf/yang/types/rev100924/IetfYangUtil.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924; + +import com.google.common.annotations.Beta; +import org.opendaylight.mdsal.model.ietf.util.AbstractIetfYangUtil; + +/** + * Utility methods for working with types defined in ietf-yang-types. + */ +@Beta +public final class IetfYangUtil extends AbstractIetfYangUtil { + public static final IetfYangUtil INSTANCE = new IetfYangUtil(); + + private IetfYangUtil() { + super(MacAddress.class); + } + + @Override + protected String getValue(final MacAddress macAddress) { + return macAddress.getValue(); + } +} diff --git a/model/ietf/pom.xml b/model/ietf/pom.xml index 4376c742b3..61f23e548d 100644 --- a/model/ietf/pom.xml +++ b/model/ietf/pom.xml @@ -37,6 +37,8 @@ ietf-restconf ietf-inet-types-2013-07-15 + + ietf-type-util