BUG-4661: Introduce Decimal64, Empty, Uint{8,16,32,64} 97/31897/21
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 10 Oct 2017 16:01:39 +0000 (18:01 +0200)
committerRobert Varga <nite@hq.sk>
Thu, 19 Oct 2017 08:25:19 +0000 (08:25 +0000)
This introduces specialized classes for native YANG types. The types
are not used yet.

Change-Id: I356f134c27ea4c8e593da067c2a5e8f2c989bbea
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Decimal64.java [new file with mode: 0644]
yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Empty.java [new file with mode: 0644]
yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Uint16.java [new file with mode: 0644]
yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Uint32.java [new file with mode: 0644]
yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Uint64.java [new file with mode: 0644]
yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Uint8.java [new file with mode: 0644]
yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/Decimal64Test.java [new file with mode: 0644]
yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/EmptyTest.java [new file with mode: 0644]

diff --git a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Decimal64.java b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Decimal64.java
new file mode 100644 (file)
index 0000000..5b7bc50
--- /dev/null
@@ -0,0 +1,293 @@
+/*
+ * Copyright (c) 2015 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.common;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Verify.verify;
+
+import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.common.primitives.Longs;
+import java.math.BigDecimal;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+/**
+ * Dedicated type for YANG's 'type decimal64' type. This class is similar to {@link BigDecimal}, but provides more
+ * efficient storage, as it has fixed precision.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public final class Decimal64 extends Number implements Comparable<Decimal64>, Immutable {
+    private static final long serialVersionUID = 1L;
+
+    private static final int MAX_FRACTION_DIGITS = 18;
+
+    private static final long[] SCALE = {
+        10,
+        100,
+        1000,
+        10000,
+        100000,
+        1000000,
+        10000000,
+        100000000,
+        1000000000,
+        10000000000L,
+        100000000000L,
+        1000000000000L,
+        10000000000000L,
+        100000000000000L,
+        1000000000000000L,
+        10000000000000000L,
+        100000000000000000L,
+        1000000000000000000L
+    };
+
+    static {
+        verify(SCALE.length == MAX_FRACTION_DIGITS);
+    }
+
+    private final byte scaleOffset;
+    private final long value;
+
+    @VisibleForTesting
+    Decimal64(final int fractionDigits, final long intPart, final long fracPart, final boolean negative) {
+        checkArgument(fractionDigits >= 1 && fractionDigits <= MAX_FRACTION_DIGITS);
+        this.scaleOffset = (byte) (fractionDigits - 1);
+
+        final long bits = intPart * SCALE[this.scaleOffset] + fracPart;
+        this.value = negative ? -bits : bits;
+    }
+
+    public static Decimal64 valueOf(final byte byteVal) {
+        return byteVal < 0 ? new Decimal64(1, -byteVal, 0, true) : new Decimal64(1, byteVal, 0, false);
+    }
+
+    public static Decimal64 valueOf(final short shortVal) {
+        return shortVal < 0 ? new Decimal64(1, -shortVal, 0, true) : new Decimal64(1, shortVal, 0, false);
+    }
+
+    public static Decimal64 valueOf(final int intVal) {
+        return intVal < 0 ? new Decimal64(1, -intVal, 0, true) : new Decimal64(1, intVal, 0, false);
+    }
+
+    public static Decimal64 valueOf(final long longVal) {
+        // XXX: we should be able to do something smarter here
+        return valueOf(Long.toString(longVal));
+    }
+
+    public static Decimal64 valueOf(final double doubleVal) {
+        // XXX: we should be able to do something smarter here
+        return valueOf(Double.toString(doubleVal));
+    }
+
+    public static Decimal64 valueOf(final BigDecimal decimalVal) {
+        // XXX: we should be able to do something smarter here
+        return valueOf(decimalVal.toPlainString());
+    }
+
+    /**
+     * Attempt to parse a String into a Decimal64. This method uses minimum fraction digits required to hold
+     * the entire value.
+     *
+     * @param str String to parser
+     * @return A Decimal64 instance
+     * @throws NullPointerException if value is null.
+     * @throws NumberFormatException if the string does not contain a parsable decimal64.
+     */
+    public static Decimal64 valueOf(final String str) {
+        // https://tools.ietf.org/html/rfc6020#section-9.3.1
+        //
+        // A decimal64 value is lexically represented as an optional sign ("+"
+        // or "-"), followed by a sequence of decimal digits, optionally
+        // followed by a period ('.') as a decimal indicator and a sequence of
+        // decimal digits.  If no sign is specified, "+" is assumed.
+        if (str.isEmpty()) {
+            throw new NumberFormatException("Empty string is not a valid decimal64 representation");
+        }
+
+        // Deal with optional sign
+        final boolean negative;
+        int idx;
+        switch (str.charAt(0)) {
+            case '-':
+                negative = true;
+                idx = 1;
+                break;
+            case '+':
+                negative = false;
+                idx = 1;
+                break;
+            default:
+                negative = false;
+                idx = 0;
+        }
+
+        // Sanity check length
+        if (idx == str.length()) {
+            throw new NumberFormatException("Missing digits after sign");
+        }
+
+        // Character limit, used for caching and cutting trailing zeroes
+        int limit = str.length() - 1;
+
+        // Skip any leading zeroes, but leave at least one
+        for (; idx < limit && str.charAt(idx) == '0'; idx++) {
+            final char ch = str.charAt(idx + 1);
+            if (ch < '0' || ch > '9') {
+                break;
+            }
+        }
+
+        // Integer part and its length
+        int intLen = 0;
+        long intPart = 0;
+
+        for (; idx <= limit; idx++, intLen++) {
+            final char ch = str.charAt(idx);
+            if (ch == '.') {
+                // Fractions are next
+                break;
+            }
+            if (intLen == MAX_FRACTION_DIGITS) {
+                throw new NumberFormatException("Integer part is longer than " + MAX_FRACTION_DIGITS + " digits");
+            }
+
+            intPart = 10 * intPart + toInt(ch, idx);
+        }
+
+        if (idx > limit) {
+            // No fraction digits, we are done
+            return new Decimal64((byte)1, intPart, 0, negative);
+        }
+
+        // Bump index to skip over period and check the remainder
+        idx++;
+        if (idx > limit) {
+            throw new NumberFormatException("Value '" + str + "' is missing fraction digits");
+        }
+
+        // Trim trailing zeroes, if any
+        while (idx < limit && str.charAt(limit) == '0') {
+            limit--;
+        }
+
+        final int fracLimit = MAX_FRACTION_DIGITS - intLen;
+        byte fracLen = 0;
+        long fracPart = 0;
+        for (; idx <= limit; idx++, fracLen++) {
+            final char ch = str.charAt(idx);
+            if (fracLen == fracLimit) {
+                throw new NumberFormatException("Fraction part longer than " + fracLimit + " digits");
+            }
+
+            fracPart = 10 * fracPart + toInt(ch, idx);
+        }
+
+        return new Decimal64(fracLen, intPart, fracPart, negative);
+    }
+
+    public BigDecimal decimalValue() {
+        return BigDecimal.valueOf(value, scaleOffset + 1);
+    }
+
+    @Override
+    public int intValue() {
+        return (int) intPart();
+    }
+
+    @Override
+    public long longValue() {
+        return intPart();
+    }
+
+    @Override
+    public float floatValue() {
+        return (float) doubleValue();
+    }
+
+    @Override
+    public double doubleValue() {
+        return 1.0 * value / SCALE[scaleOffset];
+    }
+
+    @Override
+    @SuppressWarnings("checkstyle:parameterName")
+    public int compareTo(final Decimal64 o) {
+        if (this == o) {
+            return 0;
+        }
+        if (scaleOffset == o.scaleOffset) {
+            return Long.compare(value, o.value);
+        }
+
+        // XXX: we could do something smarter here
+        return Double.compare(doubleValue(), o.doubleValue());
+    }
+
+    @Override
+    public int hashCode() {
+        // We need to normalize the results in order to be consistent with equals()
+        return Longs.hashCode(intPart()) * 31 + Long.hashCode(fracPart());
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof Decimal64)) {
+            return false;
+        }
+        final Decimal64 other = (Decimal64) obj;
+        if (scaleOffset == other.scaleOffset) {
+            return value == other.value;
+        }
+
+        // We need to normalize both
+        return intPart() == other.intPart() && fracPart() == fracPart();
+    }
+
+    @Override
+    public String toString() {
+        // https://tools.ietf.org/html/rfc6020#section-9.3.2
+        //
+        // The canonical form of a positive decimal64 does not include the sign
+        // "+".  The decimal point is required.  Leading and trailing zeros are
+        // prohibited, subject to the rule that there MUST be at least one digit
+        // before and after the decimal point.  The value zero is represented as
+        // "0.0".
+        final StringBuilder sb = new StringBuilder(21).append(intPart()).append('.');
+        final long fracPart = fracPart();
+        if (fracPart != 0) {
+            // We may need to zero-pad the fraction part
+            sb.append(Strings.padStart(Long.toString(fracPart), scaleOffset + 1, '0'));
+        } else {
+            sb.append('0');
+        }
+
+        return sb.toString();
+    }
+
+    private long intPart() {
+        return value / SCALE[scaleOffset];
+    }
+
+    private long fracPart() {
+        return Math.abs(value % SCALE[scaleOffset]);
+    }
+
+    private static int toInt(final char ch, final int index) {
+        if (ch < '0' || ch > '9') {
+            throw new NumberFormatException("Illegal character at offset " + index);
+        }
+        return ch - '0';
+    }
+}
diff --git a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Empty.java b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Empty.java
new file mode 100644 (file)
index 0000000..ee2e691
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015 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.common;
+
+import com.google.common.annotations.Beta;
+import java.io.Serializable;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+/**
+ * Dedicated singleton type for YANG's 'type empty' value.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public final class Empty implements Immutable, Serializable {
+    private static final long serialVersionUID = 1L;
+    private static final Empty INSTANCE = new Empty();
+
+    private Empty() {
+
+    }
+
+    public static Empty getInstance() {
+        return INSTANCE;
+    }
+
+    @Override
+    public String toString() {
+        return "empty";
+    }
+
+    @SuppressWarnings("static-method")
+    private Object readResolve() {
+        return INSTANCE;
+    }
+}
diff --git a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Uint16.java b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Uint16.java
new file mode 100644 (file)
index 0000000..882ae2f
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2015 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.common;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.Beta;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+/**
+ * Dedicated type for YANG's 'type uint16' type.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public final class Uint16 extends Number implements Comparable<Uint16>, Immutable {
+    private static final long serialVersionUID = 1L;
+    private static final int MIN_VALUE = 0;
+    private static final int MAX_VALUE = 65535;
+
+    /**
+     * Cache of first 256 values.
+     */
+    private static final Uint16[] CACHE = new Uint16[Uint8.MAX_VALUE];
+
+    /**
+     * Commonly encountered values.
+     */
+    private static final Uint16[] COMMON = new Uint16[] {
+        new Uint16(Short.MAX_VALUE),
+        new Uint16((short)32768),
+        new Uint16((short)65535),
+    };
+
+    /**
+     * Tunable weak LRU cache for other values. By default it holds {@value #DEFAULT_LRU_SIZE} entries. This can be
+     * changed via {@value #LRU_SIZE_PROPERTY} system property.
+     */
+    private static final int DEFAULT_LRU_SIZE = 1024;
+    private static final String LRU_SIZE_PROPERTY = "org.opendaylight.yangtools.yang.common.Uint16.LRU.size";
+    private static final int MAX_LRU_SIZE = MAX_VALUE + 1;
+    private static final int LRU_SIZE;
+
+    static {
+        final int p = Integer.getInteger(LRU_SIZE_PROPERTY, DEFAULT_LRU_SIZE);
+        LRU_SIZE = p >= 0 ? Math.min(p, MAX_LRU_SIZE) : DEFAULT_LRU_SIZE;
+    }
+
+    private static final LoadingCache<Short, Uint16> LRU = CacheBuilder.newBuilder().weakValues().maximumSize(LRU_SIZE)
+            .build(new CacheLoader<Short, Uint16>() {
+                @Override
+                public Uint16 load(final Short key) {
+                    return new Uint16(key);
+                }
+            });
+
+    private final short value;
+
+    private Uint16(final short value) {
+        this.value = value;
+    }
+
+    private static Uint16 instanceFor(final short value) {
+        final int slot = Short.toUnsignedInt(value);
+        if (slot >= CACHE.length) {
+            for (Uint16 c : COMMON) {
+                if (c.value == value) {
+                    return c;
+                }
+            }
+
+            return LRU.getUnchecked(value);
+        }
+
+        Uint16 ret = CACHE[slot];
+        if (ret == null) {
+            synchronized (CACHE) {
+                ret = CACHE[slot];
+                if (ret == null) {
+                    ret = new Uint16(value);
+                    CACHE[slot] = ret;
+                }
+            }
+        }
+
+        return ret;
+    }
+
+    public static Uint16 fromShortBits(final short bits) {
+        return instanceFor(bits);
+    }
+
+    public static Uint16 valueOf(final byte byteVal) {
+        checkArgument(byteVal >= MIN_VALUE, "Negative values are not allowed");
+        return instanceFor(byteVal);
+    }
+
+    public static Uint16 valueOf(final short shortVal) {
+        checkArgument(shortVal >= MIN_VALUE, "Negative values are not allowed");
+        return instanceFor(shortVal);
+    }
+
+    public static Uint16 valueOf(final int intVal) {
+        checkArgument(intVal >= MIN_VALUE && intVal <= MAX_VALUE, "Value %s is outside of allowed range", intVal);
+        return instanceFor((short)(intVal & 0xffff));
+    }
+
+    public static Uint16 valueOf(final long longVal) {
+        checkArgument(longVal >= MIN_VALUE && longVal <= MAX_VALUE, "Value %s is outside of allowed range", longVal);
+        return instanceFor((short)(longVal & 0xffff));
+    }
+
+    public static Uint16 valueOf(final Uint8 uint) {
+        return instanceFor(uint.shortValue());
+    }
+
+    public static Uint16 valueOf(final Uint32 uint) {
+        return valueOf(uint.longValue());
+    }
+
+    public static Uint16 valueOf(final String string) {
+        return valueOf(string, 10);
+    }
+
+    public static Uint16 valueOf(final String string, final int radix) {
+        // FIXME: implement this
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public short shortValue() {
+        return value;
+    }
+
+    @Override
+    public int intValue() {
+        return Short.toUnsignedInt(value);
+    }
+
+    @Override
+    public long longValue() {
+        return Short.toUnsignedLong(value);
+    }
+
+    @Override
+    public float floatValue() {
+        return intValue();
+    }
+
+    @Override
+    public double doubleValue() {
+        return intValue();
+    }
+
+    @Override
+    @SuppressWarnings("checkstyle:parameterName")
+    public int compareTo(final Uint16 o) {
+        return Integer.compare(intValue(), o.intValue());
+    }
+
+    @Override
+    public int hashCode() {
+        return Short.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        return obj instanceof Uint16 && value == ((Uint16)obj).value;
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(intValue());
+    }
+
+    private Object readResolve() {
+        return instanceFor(value);
+    }
+}
diff --git a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Uint32.java b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Uint32.java
new file mode 100644 (file)
index 0000000..613d586
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2015 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.common;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.Beta;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.primitives.UnsignedInteger;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+/**
+ * Dedicated type for YANG's 'type uint32' type.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public final class Uint32 extends Number implements Comparable<Uint32>, Immutable {
+    private static final long serialVersionUID = 1L;
+    private static final long MIN_VALUE = 0;
+    private static final long MAX_VALUE = 0xffffffffL;
+
+    /**
+     * Cache of first 256 values.
+     */
+    private static final Uint32[] CACHE = new Uint32[Uint8.MAX_VALUE];
+    /**
+     * Commonly encountered values.
+     */
+    private static final Uint32[] COMMON = {
+        new Uint32(Short.MAX_VALUE),
+        new Uint32(32768),
+        new Uint32(65535),
+        new Uint32(65536),
+        new Uint32(Integer.MAX_VALUE),
+    };
+
+    /**
+     * Tunable weak LRU cache for other values. By default it holds {@value #DEFAULT_LRU_SIZE} entries. This can be
+     * changed via {@value #LRU_SIZE_PROPERTY} system property.
+     */
+    private static final int DEFAULT_LRU_SIZE = 1024;
+    private static final String LRU_SIZE_PROPERTY = "org.opendaylight.yangtools.yang.common.Uint32.LRU.size";
+    private static final int MAX_LRU_SIZE = 0xffffff;
+    private static final int LRU_SIZE;
+
+    static {
+        final int p = Integer.getInteger(LRU_SIZE_PROPERTY, DEFAULT_LRU_SIZE);
+        LRU_SIZE = p >= 0 ? Math.min(p, MAX_LRU_SIZE) : DEFAULT_LRU_SIZE;
+    }
+
+    private static final LoadingCache<Integer, Uint32> LRU = CacheBuilder.newBuilder().weakValues()
+        .maximumSize(LRU_SIZE).build(new CacheLoader<Integer, Uint32>() {
+                @Override
+                public Uint32 load(final Integer key) {
+                    return new Uint32(key);
+                }
+            });
+
+    private final int value;
+
+    private Uint32(final int value) {
+        this.value = value;
+    }
+
+    private static Uint32 instanceFor(final int value) {
+        final long longSlot = Integer.toUnsignedLong(value);
+        if (longSlot >= CACHE.length) {
+            for (Uint32 c : COMMON) {
+                if (c.value == value) {
+                    return c;
+                }
+            }
+
+            return LRU.getUnchecked(value);
+        }
+
+        final int slot = (int)longSlot;
+        Uint32 ret = CACHE[slot];
+        if (ret == null) {
+            synchronized (CACHE) {
+                ret = CACHE[slot];
+                if (ret == null) {
+                    ret = new Uint32(value);
+                    CACHE[slot] = ret;
+                }
+            }
+        }
+
+        return ret;
+    }
+
+    public static Uint32 fromIntBits(final int bits) {
+        return instanceFor(bits);
+    }
+
+    public static Uint32 fromUnsignedInteger(final UnsignedInteger uint) {
+        return instanceFor(uint.intValue());
+    }
+
+    public static Uint32 valueOf(final byte byteVal) {
+        checkArgument(byteVal >= MIN_VALUE, "Negative values are not allowed");
+        return instanceFor(byteVal);
+    }
+
+    public static Uint32 valueOf(final short shortVal) {
+        checkArgument(shortVal >= MIN_VALUE, "Negative values are not allowed");
+        return instanceFor(shortVal);
+    }
+
+    public static Uint32 valueOf(final int intVal) {
+        checkArgument(intVal >= MIN_VALUE, "Value %s is outside of allowed range", intVal);
+        return instanceFor(intVal);
+    }
+
+    public static Uint32 valueOf(final long longVal) {
+        checkArgument(longVal >= MIN_VALUE && longVal <= MAX_VALUE, "Value %s is outside of allowed range", longVal);
+        return instanceFor((int)longVal);
+    }
+
+    public static Uint32 valueOf(final Uint8 uint) {
+        return instanceFor(uint.shortValue());
+    }
+
+    public static Uint32 valueOf(final Uint16 uint) {
+        return instanceFor(uint.intValue());
+    }
+
+    public static Uint32 valueOf(final String string) {
+        return valueOf(string, 10);
+    }
+
+    public static Uint32 valueOf(final String string, final int radix) {
+        return instanceFor(Integer.parseUnsignedInt(string, radix));
+    }
+
+    @Override
+    public int intValue() {
+        return value;
+    }
+
+    @Override
+    public long longValue() {
+        return Integer.toUnsignedLong(value);
+    }
+
+    @Override
+    public float floatValue() {
+        return longValue();
+    }
+
+    @Override
+    public double doubleValue() {
+        return longValue();
+    }
+
+    public UnsignedInteger toUnsignedInteger() {
+        return UnsignedInteger.fromIntBits(value);
+    }
+
+    @Override
+    @SuppressWarnings("checkstyle:parameterName")
+    public int compareTo(final Uint32 o) {
+        return Integer.compareUnsigned(value, o.value);
+    }
+
+    @Override
+    public int hashCode() {
+        return Integer.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        return obj instanceof Uint32 && value == ((Uint32)obj).value;
+    }
+
+    @Override
+    public String toString() {
+        return Integer.toUnsignedString(value);
+    }
+
+    private Object readResolve() {
+        return instanceFor(value);
+    }
+}
diff --git a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Uint64.java b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Uint64.java
new file mode 100644 (file)
index 0000000..743a8e3
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2015 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.common;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.Beta;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.primitives.UnsignedLong;
+import java.math.BigInteger;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+/**
+ * Dedicated type for YANG's 'type uint64' type.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public final class Uint64 extends Number implements Comparable<Uint64>, Immutable {
+    private static final long serialVersionUID = 1L;
+    private static final long MIN_VALUE = 0;
+
+    /**
+     * Cache of first 256 values.
+     */
+    private static final Uint64[] CACHE = new Uint64[Uint8.MAX_VALUE];
+    /**
+     * Commonly encountered values.
+     */
+    private static final Uint64[] COMMON = {
+        new Uint64(Short.MAX_VALUE + 1L),
+        new Uint64(32768),
+        new Uint64(65535),
+        new Uint64(65536),
+        new Uint64(Integer.MAX_VALUE),
+        new Uint64(Integer.MAX_VALUE + 1L),
+        new Uint64(Long.MAX_VALUE),
+    };
+
+    /**
+     * Tunable weak LRU cache for other values. By default it holds {@value #DEFAULT_LRU_SIZE} entries. This can be
+     * changed via {@value #LRU_SIZE_PROPERTY} system property.
+     */
+    private static final int DEFAULT_LRU_SIZE = 1024;
+    private static final String LRU_SIZE_PROPERTY = "org.opendaylight.yangtools.yang.common.Uint64.LRU.size";
+    private static final int MAX_LRU_SIZE = 0xffffff;
+    private static final int LRU_SIZE;
+
+    static {
+        final int p = Integer.getInteger(LRU_SIZE_PROPERTY, DEFAULT_LRU_SIZE);
+        LRU_SIZE = p >= 0 ? Math.min(p, MAX_LRU_SIZE) : DEFAULT_LRU_SIZE;
+    }
+
+    private static final LoadingCache<Long, Uint64> LRU = CacheBuilder.newBuilder().weakValues().maximumSize(LRU_SIZE)
+            .build(new CacheLoader<Long, Uint64>() {
+                @Override
+                public Uint64 load(final Long key) {
+                    return new Uint64(key);
+                }
+            });
+
+    private final long value;
+
+    private Uint64(final long value) {
+        this.value = value;
+    }
+
+    private static Uint64 instanceFor(final long value) {
+        final int slot = (int)value;
+        if (value < 0 || slot >= CACHE.length) {
+            for (Uint64 c : COMMON) {
+                if (c.value == value) {
+                    return c;
+                }
+            }
+
+            return LRU.getUnchecked(value);
+        }
+
+        Uint64 ret = CACHE[slot];
+        if (ret == null) {
+            synchronized (CACHE) {
+                ret = CACHE[slot];
+                if (ret == null) {
+                    ret = new Uint64(value);
+                    CACHE[slot] = ret;
+                }
+            }
+        }
+
+        return ret;
+    }
+
+    public static Uint64 fromLongBits(final long bits) {
+        return instanceFor(bits);
+    }
+
+    public static Uint64 fromUnsignedLong(final UnsignedLong ulong) {
+        return instanceFor(ulong.longValue());
+    }
+
+    public static Uint64 valueOf(final byte byteVal) {
+        checkArgument(byteVal >= MIN_VALUE, "Negative values are not allowed");
+        return instanceFor(byteVal);
+    }
+
+    public static Uint64 valueOf(final short shortVal) {
+        checkArgument(shortVal >= MIN_VALUE, "Negative values are not allowed");
+        return instanceFor(shortVal);
+    }
+
+    public static Uint64 valueOf(final int intVal) {
+        checkArgument(intVal >= MIN_VALUE, "Value %s is outside of allowed range", intVal);
+        return instanceFor(intVal);
+    }
+
+    public static Uint64 valueOf(final long longVal) {
+        checkArgument(longVal >= MIN_VALUE, "Value %s is outside of allowed range", longVal);
+        return instanceFor(longVal);
+    }
+
+    public static Uint64 valueOf(final Uint8 uint) {
+        return instanceFor(uint.shortValue());
+    }
+
+    public static Uint64 valueOf(final Uint16 uint) {
+        return instanceFor(uint.intValue());
+    }
+
+    public static Uint64 valueOf(final Uint32 uint) {
+        return instanceFor(uint.longValue());
+    }
+
+    public static Uint64 valueOf(final String string) {
+        return valueOf(string, 10);
+    }
+
+    public static Uint64 valueOf(final String string, final int radix) {
+        return instanceFor(Long.parseUnsignedLong(string, radix));
+    }
+
+    public static Uint64 valueOf(final BigInteger bigInt) {
+        checkArgument(bigInt.signum() >= 0, "Negative values not allowed");
+        checkArgument(bigInt.bitLength() <= Long.SIZE, "Value %s is outside of allowed range", bigInt);
+
+        return instanceFor(bigInt.longValue());
+    }
+
+    @Override
+    public int intValue() {
+        return (int)value;
+    }
+
+    @Override
+    public long longValue() {
+        return value;
+    }
+
+    @Override
+    public float floatValue() {
+        // TODO: ditch Guava
+        return UnsignedLong.fromLongBits(value).floatValue();
+    }
+
+    @Override
+    public double doubleValue() {
+        // TODO: ditch Guava
+        return UnsignedLong.fromLongBits(value).doubleValue();
+    }
+
+    public UnsignedLong toUnsignedLong() {
+        return UnsignedLong.fromLongBits(value);
+    }
+
+    @Override
+    @SuppressWarnings("checkstyle:parameterName")
+    public int compareTo(final Uint64 o) {
+        return Long.compareUnsigned(value, o.value);
+    }
+
+    @Override
+    public int hashCode() {
+        return Long.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        return obj instanceof Uint64 && value == ((Uint64)obj).value;
+    }
+
+    @Override
+    public String toString() {
+        return Long.toUnsignedString(value);
+    }
+
+    private Object readResolve() {
+        return instanceFor(value);
+    }
+}
diff --git a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Uint8.java b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Uint8.java
new file mode 100644 (file)
index 0000000..997cfdf
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2015 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.common;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.Beta;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+/**
+ * Dedicated type for YANG's 'type uint8' type.
+ *
+ * @author Robert Varga
+ */
+@Beta
+public final class Uint8 extends Number implements Comparable<Uint8>, Immutable {
+    static final short MIN_VALUE = 0;
+    static final short MAX_VALUE = 255;
+
+    private static final long serialVersionUID = 1L;
+    private static final Uint8[] CACHE = new Uint8[MAX_VALUE + 1];
+
+    private final byte value;
+
+    private Uint8(final byte value) {
+        this.value = value;
+    }
+
+    private static Uint8 instanceFor(final byte value) {
+        final int slot = Byte.toUnsignedInt(value);
+
+        Uint8 ret = CACHE[slot];
+        if (ret == null) {
+            synchronized (CACHE) {
+                ret = CACHE[slot];
+                if (ret == null) {
+                    ret = new Uint8(value);
+                    CACHE[slot] = ret;
+                }
+            }
+        }
+
+        return ret;
+    }
+
+    public static Uint8 fromByteBits(final byte bits) {
+        return instanceFor(bits);
+    }
+
+    public static Uint8 valueOf(final byte byteVal) {
+        checkArgument(byteVal >= MIN_VALUE, "Negative values are not allowed");
+        return instanceFor(byteVal);
+    }
+
+    public static Uint8 valueOf(final short shortVal) {
+        checkArgument(shortVal >= MIN_VALUE && shortVal <= MAX_VALUE, "Value %s is outside of allowed range", shortVal);
+        return instanceFor((byte)(shortVal & 0xff));
+    }
+
+    public static Uint8 valueOf(final int intVal) {
+        checkArgument(intVal >= MIN_VALUE && intVal <= MAX_VALUE, "Value %s is outside of allowed range", intVal);
+        return instanceFor((byte)(intVal & 0xff));
+    }
+
+    public static Uint8 valueOf(final long longVal) {
+        checkArgument(longVal >= MIN_VALUE && longVal <= MAX_VALUE, "Value %s is outside of allowed range", longVal);
+        return instanceFor((byte)(longVal & 0xff));
+    }
+
+    public static Uint8 valueOf(final Uint16 uint) {
+        return valueOf(uint.intValue());
+    }
+
+    public static Uint8 valueOf(final String string) {
+        return valueOf(string, 10);
+    }
+
+    public static Uint8 valueOf(final String string, final int radix) {
+        // FIXME: implement this
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public byte byteValue() {
+        return value;
+    }
+
+    @Override
+    public int intValue() {
+        return Byte.toUnsignedInt(value);
+    }
+
+    @Override
+    public long longValue() {
+        return Byte.toUnsignedLong(value);
+    }
+
+    @Override
+    public float floatValue() {
+        return intValue();
+    }
+
+    @Override
+    public double doubleValue() {
+        return intValue();
+    }
+
+    @Override
+    @SuppressWarnings("checkstyle:parameterName")
+    public int compareTo(final Uint8 o) {
+        return intValue() - o.intValue();
+    }
+
+    @Override
+    public int hashCode() {
+        return Byte.hashCode(value);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        return obj instanceof Uint8 && value == ((Uint8)obj).value;
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(intValue());
+    }
+
+    private Object readResolve() {
+        return instanceFor(value);
+    }
+}
diff --git a/yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/Decimal64Test.java b/yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/Decimal64Test.java
new file mode 100644 (file)
index 0000000..ff610a2
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2017 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.common;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import org.junit.Test;
+
+public class Decimal64Test {
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseEmpty() {
+        Decimal64.valueOf("");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseSingleIllegal() {
+        Decimal64.valueOf("a");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseSingleHighIllegal() {
+        Decimal64.valueOf(":");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseZeroIllegal() {
+        Decimal64.valueOf("0a");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseZeroHighIllegal() {
+        Decimal64.valueOf("0:");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseZeroPointIllegal() {
+        Decimal64.valueOf("0.a");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseZeroPointHighIllegal() {
+        Decimal64.valueOf("0.:");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParsePointIllegal() {
+        Decimal64.valueOf(".a");
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testParseNull() {
+        Decimal64.valueOf((String)null);
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseMinus() {
+        Decimal64.valueOf("-");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParsePlus() {
+        Decimal64.valueOf("+");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParsePeriod() {
+        Decimal64.valueOf(".");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseTwoPeriods() {
+        Decimal64.valueOf("..");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseTrailingPeriod() {
+        Decimal64.valueOf("0.");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseMultiplePeriods() {
+        Decimal64.valueOf("0.1.");
+    }
+
+    @Test
+    public void testParseLongString() {
+        Decimal64.valueOf("123456789012345678");
+    }
+
+    @Test
+    public void testParseLongDecimal() {
+        Decimal64.valueOf("0.12345678901234568");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseTooLongString() {
+        Decimal64.valueOf("1234567890123456789");
+    }
+
+    @Test(expected = NumberFormatException.class)
+    public void testParseTooLongDecimal() {
+        Decimal64.valueOf("0.123456789012345689");
+    }
+
+    @Test
+    public void testParse() {
+        assertParsedVariants("0", 0, 0, 1);
+        assertParsedVariants("0.00", 0, 0, 1);
+        assertParsedVariants("00.0", 0, 0, 1);
+        assertParsedVariants("000.0", 0, 0, 1);
+        assertCanonicalVariants("10.0", 10, 0, 1);
+        assertCanonicalVariants("10.09", 10, 9, 2);
+        assertParsedVariants("10.0900900", 10, 9009, 5);
+        assertParsedVariants("0002210.09", 2210, 9, 2);
+
+        Decimal64 parsed = assertParsedString("0.0", 0, 0, 1, false);
+        parsed = assertParsedString("+0.0", 0, 0, 1, false);
+        assertEquals("0.0", parsed.toString());
+        assertEquals("0.0", parsed.toString());
+        parsed = assertParsedString("-0.0", 0, 0, 1, true);
+        assertEquals("0.0", parsed.toString());
+
+        assertCanonicalVariants("1.0", 1, 0, 1);
+        assertCanonicalVariants("2.3", 2, 3, 1);
+    }
+
+    @Test
+    public void testCompare() {
+        final Decimal64 one = Decimal64.valueOf("1");
+        final Decimal64 two = Decimal64.valueOf("2");
+        final Decimal64 three = Decimal64.valueOf("3");
+        final Decimal64 negOne = Decimal64.valueOf("-1");
+        final Decimal64 anotherOne = Decimal64.valueOf("1");
+
+        assertEquals(0, one.compareTo(one));
+        assertEquals(0, one.compareTo(anotherOne));
+        assertEquals(-1, one.compareTo(two));
+        assertEquals(-1, one.compareTo(three));
+        assertEquals(1, one.compareTo(negOne));
+
+        assertEquals(1, two.compareTo(one));
+        assertEquals(1, two.compareTo(anotherOne));
+        assertEquals(-1, two.compareTo(three));
+        assertEquals(1, one.compareTo(negOne));
+    }
+
+    @Test
+    public void testEquals() {
+        final Decimal64 one = Decimal64.valueOf("1");
+        final Decimal64 two = Decimal64.valueOf("2");
+        final Decimal64 anotherOne = Decimal64.valueOf("1");
+
+        assertTrue(one.equals(one));
+        assertTrue(one.equals(anotherOne));
+        assertFalse(one.equals(two));
+        assertTrue(two.equals(two));
+        assertFalse(two.equals(one));
+
+        assertFalse(one.equals(new Object()));
+    }
+
+    @Test
+    public void testConversions() {
+        assertEquals(new BigDecimal("0.12"), Decimal64.valueOf("0.12").decimalValue());
+        assertEquals(new BigDecimal("-0.12"), Decimal64.valueOf("-0.12").decimalValue());
+        assertEquals(new BigDecimal("0.12"), Decimal64.valueOf("+0.12").decimalValue());
+        assertEquals(new BigDecimal("123.456"), Decimal64.valueOf("123.456").decimalValue());
+        assertEquals(new BigDecimal("-123.456"), Decimal64.valueOf("-123.456").decimalValue());
+
+        assertEquals(0.12, Decimal64.valueOf("0.12").doubleValue(), 0);
+        assertEquals(-0.12, Decimal64.valueOf("-0.12").doubleValue(), 0);
+
+        assertEquals((float) 0.12, Decimal64.valueOf("0.12").floatValue(), 0);
+        assertEquals((float) -0.12, Decimal64.valueOf("-0.12").floatValue(), 0);
+
+        assertEquals(12345678901L, Decimal64.valueOf("12345678901").longValue());
+        assertEquals(-12345678901L, Decimal64.valueOf("-12345678901").longValue());
+    }
+
+    @Test
+    public void testFactories() {
+        assertEquals("0.0", Decimal64.valueOf((byte) 0).toString());
+        assertEquals("1.0", Decimal64.valueOf((byte) 1).toString());
+        assertEquals("-1.0", Decimal64.valueOf((byte) -1).toString());
+
+        assertEquals("0.0", Decimal64.valueOf((short) 0).toString());
+        assertEquals("1.0", Decimal64.valueOf((short) 1).toString());
+        assertEquals("-1.0", Decimal64.valueOf((short) -1).toString());
+
+        assertEquals("0.0", Decimal64.valueOf(0).toString());
+        assertEquals("1.0", Decimal64.valueOf(1).toString());
+        assertEquals("-1.0", Decimal64.valueOf(-1).toString());
+
+        assertEquals("0.0", Decimal64.valueOf(0L).toString());
+        assertEquals("1.0", Decimal64.valueOf(1L).toString());
+        assertEquals("-1.0", Decimal64.valueOf(-1L).toString());
+
+        assertEquals("0.0", Decimal64.valueOf(0.0).toString());
+        assertEquals("1.0", Decimal64.valueOf(1.0).toString());
+        assertEquals("-1.0", Decimal64.valueOf(-1.0).toString());
+
+        assertEquals("0.0", Decimal64.valueOf(BigDecimal.ZERO).toString());
+        assertEquals("1.0", Decimal64.valueOf(BigDecimal.ONE).toString());
+        assertEquals("-1.0", Decimal64.valueOf(BigDecimal.ONE.negate()).toString());
+    }
+
+    private static void assertCanonicalVariants(final String str, final long intPart, final long fracPart,
+            final int digits) {
+        assertCanonicalString(str, intPart, fracPart, digits, false);
+        assertCanonicalString("-" + str, intPart, fracPart, digits, true);
+
+        final Decimal64 parsed = assertParsedString("+" + str, intPart, fracPart, digits, false);
+        assertEquals(str, parsed.toString());
+    }
+
+    private static void assertParsedVariants(final String str, final long intPart, final long fracPart,
+            final int digits) {
+        assertParsedString(str, intPart, fracPart, digits, false);
+        assertParsedString("-" + str, intPart, fracPart, digits, true);
+        assertParsedString("+" + str, intPart, fracPart, digits, false);
+    }
+
+    private static void assertCanonicalString(final String str, final long intPart, final long fracPart,
+            final int digits, final boolean negative) {
+        final Decimal64 parsed = assertParsedString(str, intPart, fracPart, digits, negative);
+        assertEquals(str, parsed.toString());
+    }
+
+    private static Decimal64 assertParsedString(final String str, final long intPart, final long fracPart,
+            final int digits, final boolean negative) {
+        final Decimal64 parsed = Decimal64.valueOf(str);
+        assertEquals(new Decimal64((byte) digits, intPart, fracPart, negative), parsed);
+        return parsed;
+    }
+}
diff --git a/yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/EmptyTest.java b/yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/EmptyTest.java
new file mode 100644 (file)
index 0000000..4df61f9
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2017 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.common;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import org.junit.Test;
+
+public class EmptyTest {
+
+    @Test
+    public void testInstanceNotNull() {
+        assertNotNull(Empty.getInstance());
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals("empty", Empty.getInstance().toString());
+    }
+
+    @Test
+    public void testSerialization() throws IOException, ClassNotFoundException {
+        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+            oos.writeObject(Empty.getInstance());
+        }
+
+        final Object read;
+        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
+            read = ois.readObject();
+        }
+
+        assertSame(Empty.getInstance(), read);
+    }
+}