-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Add Quantity type #129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Quantity type #129
Changes from 1 commit
a295a5d
924458f
af94a84
cd4108d
6cf2c64
f37eee2
cb1bfc2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package io.kubernetes.client.custom; | ||
|
|
||
| public class BaseExponent { | ||
|
|
||
| private final int base; | ||
|
|
||
| private final int exponent; | ||
|
|
||
|
||
| private final Quantity.Format format; | ||
| public BaseExponent(int base, int exponent, Quantity.Format format) { | ||
| this.base = base; | ||
| this.exponent = exponent; | ||
| this.format = format; | ||
| } | ||
|
|
||
| public int getBase() { | ||
| return base; | ||
| } | ||
|
|
||
| public int getExponent() { | ||
| return exponent; | ||
| } | ||
|
|
||
| public Quantity.Format getFormat() { | ||
| return format; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "BaseExponent{" + | ||
| "base=" + base + | ||
| ", exponent=" + exponent + | ||
| ", format=" + format + | ||
| '}'; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) return true; | ||
| if (o == null || getClass() != o.getClass()) return false; | ||
|
|
||
| BaseExponent that = (BaseExponent) o; | ||
|
|
||
| if (base != that.base) return false; | ||
|
||
| if (exponent != that.exponent) return false; | ||
| return format == that.format; | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| int result = base; | ||
|
||
| result = 31 * result + exponent; | ||
| result = 31 * result + (format != null ? format.hashCode() : 0); | ||
| return result; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package io.kubernetes.client.custom; | ||
|
|
||
| public class Pair<L, R> { | ||
|
||
|
|
||
| private final L left; | ||
| private final R right; | ||
|
|
||
| public Pair(final L left, final R right) { | ||
| this.left = left; | ||
| this.right = right; | ||
| } | ||
|
|
||
| public L getLeft() { | ||
| return left; | ||
| } | ||
|
|
||
| public R getRight() { | ||
| return right; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| package io.kubernetes.client.custom; | ||
|
|
||
| import com.google.gson.TypeAdapter; | ||
| import com.google.gson.annotations.JsonAdapter; | ||
| import com.google.gson.stream.JsonReader; | ||
| import com.google.gson.stream.JsonWriter; | ||
|
|
||
| import java.io.IOException; | ||
| import java.math.BigDecimal; | ||
|
|
||
| @JsonAdapter(Quantity.QuantityAdapter.class) | ||
| public class Quantity { | ||
|
|
||
| private final BigDecimal number; | ||
| private Format format; | ||
|
|
||
| public enum Format { | ||
| DECIMAL_EXPONENT(10), DECIMAL_SI(10), BINARY_SI(2); | ||
|
|
||
| private int base; | ||
|
|
||
| Format(final int base) { | ||
| this.base = base; | ||
| } | ||
|
|
||
| public int getBase() { | ||
| return base; | ||
| } | ||
| } | ||
|
|
||
| public Quantity(final BigDecimal number, final Format format) { | ||
| this.number = number; | ||
| this.format = format; | ||
| } | ||
|
|
||
| public BigDecimal getBigDecimal() { | ||
|
||
| return number; | ||
| } | ||
|
|
||
| public Format getFormat() { | ||
| return format; | ||
| } | ||
|
|
||
| public static Quantity fromString(final String value) { | ||
| return new QuantityFormatter().parse(value); | ||
| } | ||
|
|
||
| public String toSuffixedString() { | ||
| return new QuantityFormatter().format(this); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "Quantity{" + | ||
| "number=" + number + | ||
| ", format=" + format + | ||
| '}'; | ||
| } | ||
|
|
||
| public class QuantityAdapter extends TypeAdapter<Quantity> { | ||
| @Override | ||
| public void write(JsonWriter jsonWriter, Quantity quantity) throws IOException { | ||
| jsonWriter.value(quantity.toSuffixedString()); | ||
| } | ||
|
|
||
| @Override | ||
| public Quantity read(JsonReader jsonReader) throws IOException { | ||
| return Quantity.fromString(jsonReader.nextString()); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package io.kubernetes.client.custom; | ||
|
|
||
| public class QuantityFormatException extends RuntimeException { | ||
| public QuantityFormatException(String s) { | ||
| super(s); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| package io.kubernetes.client.custom; | ||
|
|
||
| import java.math.BigDecimal; | ||
| import java.math.MathContext; | ||
|
|
||
| public class QuantityFormatter { | ||
|
|
||
| private static final String PARTS_RE = "[eEinumkKMGTP]+"; | ||
|
|
||
| public Quantity parse(final String value) { | ||
| if (value == null || value.isEmpty()) { | ||
| throw new QuantityFormatException(""); | ||
| } | ||
| final String[] parts = value.split(PARTS_RE); | ||
| final BigDecimal numericValue = parseNumericValue(parts[0]); | ||
| final String suffix = value.substring(parts[0].length()); | ||
| final BaseExponent baseExponent = new SuffixFormatter().parse(suffix); | ||
| final BigDecimal unitMultiplier = BigDecimal.valueOf(baseExponent.getBase()).pow(baseExponent.getExponent(), MathContext.DECIMAL64); | ||
| final BigDecimal unitlessValue = numericValue.multiply(unitMultiplier); | ||
| return new Quantity(unitlessValue, baseExponent.getFormat()); | ||
| } | ||
|
|
||
| private static BigDecimal parseNumericValue(String part) { | ||
| try { | ||
| return new BigDecimal(part); | ||
| } catch (final NumberFormatException e) { | ||
| throw new QuantityFormatException("Unable to parse numeric part of quantity: " + part); | ||
| } | ||
| } | ||
|
|
||
| public String format(final Quantity quantity) { | ||
| switch (quantity.getFormat()) { | ||
| case DECIMAL_SI: | ||
| case DECIMAL_EXPONENT: | ||
| return toBase10String(quantity); | ||
| case BINARY_SI: | ||
| if (isFractional(quantity)) { | ||
| return toBase10String(new Quantity(quantity.getBigDecimal(), Quantity.Format.DECIMAL_SI)); | ||
| } | ||
| return toBase1024String(quantity); | ||
| default: | ||
| throw new IllegalArgumentException("Can't format a " + quantity.getFormat() + " quantity"); | ||
| } | ||
| } | ||
|
|
||
| private boolean isFractional(Quantity quantity) { | ||
| return quantity.getBigDecimal().scale() > 0; | ||
| } | ||
|
|
||
| private String toBase1024String(final Quantity quantity) { | ||
| final BigDecimal amount = quantity.getBigDecimal(); | ||
| final long value = amount.unscaledValue().longValue(); | ||
| final int exponent = -amount.scale(); | ||
| final Pair<Long, Integer> resultAndTimes = removeFactorsForBase(value, 1024); | ||
| return resultAndTimes.getLeft() + new SuffixFormatter().format(quantity.getFormat(), exponent + resultAndTimes.getRight() * 10); | ||
| } | ||
|
|
||
| private String toBase10String(final Quantity quantity) { | ||
| final BigDecimal amount = quantity.getBigDecimal(); | ||
| final long value = amount.unscaledValue().longValue(); | ||
| final int exponent = -amount.scale(); | ||
| final Pair<Long, Integer> resultAndTimes = removeFactorsForBase(value, 10); | ||
| final int postFactoringExponent = exponent + resultAndTimes.getRight(); | ||
| final Pair<Long, Integer> valueAndExponent = ensureExponentIsMultipleOf3(resultAndTimes.getLeft(), postFactoringExponent); | ||
| return valueAndExponent.getLeft() + new SuffixFormatter().format(quantity.getFormat(), valueAndExponent.getRight()); | ||
| } | ||
|
|
||
| private Pair<Long, Integer> ensureExponentIsMultipleOf3(final long mantissa, final int exponent) { | ||
| final long exponentRemainder = exponent % 3; | ||
| if (exponentRemainder == 1 || exponentRemainder == -2) { | ||
| return new Pair<>(mantissa * 10, exponent - 1); | ||
| } else if (exponentRemainder == -1 || exponentRemainder == 2) { | ||
| return new Pair<>(mantissa * 100, exponent - 2); | ||
| } else { | ||
| return new Pair<>(mantissa, exponent); | ||
| } | ||
| } | ||
|
|
||
| private Pair<Long, Integer> removeFactorsForBase(final long value, final int base) { | ||
| int times = 0; | ||
| long result = value; | ||
| while (result >= base && result % base == 0) { | ||
| times++; | ||
| result = result / base; | ||
| } | ||
| return new Pair<>(result, times); | ||
| } | ||
|
|
||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| package io.kubernetes.client.custom; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| public class SuffixFormatter { | ||
|
|
||
| private static final Map<String, BaseExponent> suffixToBinary = new HashMap<String, BaseExponent>() { | ||
| { | ||
| put("", new BaseExponent(2, 0, Quantity.Format.BINARY_SI)); | ||
| put("Ki", new BaseExponent(2, 10, Quantity.Format.BINARY_SI)); | ||
| put("Mi", new BaseExponent(2, 20, Quantity.Format.BINARY_SI)); | ||
| put("Gi", new BaseExponent(2, 30, Quantity.Format.BINARY_SI)); | ||
| put("Ti", new BaseExponent(2, 40, Quantity.Format.BINARY_SI)); | ||
| put("Pi", new BaseExponent(2, 50, Quantity.Format.BINARY_SI)); | ||
| put("Ei", new BaseExponent(2, 60, Quantity.Format.BINARY_SI)); | ||
| } | ||
| }; | ||
|
|
||
| private static final Map<String, BaseExponent> suffixToDecimal = new HashMap<String, BaseExponent>() { | ||
| { | ||
| put("n", new BaseExponent(10, -9, Quantity.Format.DECIMAL_SI)); | ||
| put("u", new BaseExponent(10, -6, Quantity.Format.DECIMAL_SI)); | ||
| put("m", new BaseExponent(10, -3, Quantity.Format.DECIMAL_SI)); | ||
| put("", new BaseExponent(10, 0, Quantity.Format.DECIMAL_SI)); | ||
| put("k", new BaseExponent(10, 3, Quantity.Format.DECIMAL_SI)); | ||
| put("M", new BaseExponent(10, 6, Quantity.Format.DECIMAL_SI)); | ||
| put("G", new BaseExponent(10, 9, Quantity.Format.DECIMAL_SI)); | ||
| put("T", new BaseExponent(10, 12, Quantity.Format.DECIMAL_SI)); | ||
| put("P", new BaseExponent(10, 15, Quantity.Format.DECIMAL_SI)); | ||
| put("E", new BaseExponent(10, 18, Quantity.Format.DECIMAL_SI)); | ||
| } | ||
| }; | ||
|
|
||
| private static final Map<BaseExponent, String> decimalToSuffix = new HashMap<BaseExponent, String>() { | ||
| { | ||
| for (Entry<String, BaseExponent> entry : suffixToDecimal.entrySet()) { | ||
| put(entry.getValue(), entry.getKey()); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| private static final Map<BaseExponent, String> binaryToSuffix = new HashMap<BaseExponent, String>() { | ||
| { | ||
| for (Entry<String, BaseExponent> entry : suffixToBinary.entrySet()) { | ||
| put(entry.getValue(), entry.getKey()); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| public BaseExponent parse(final String suffix) { | ||
| final BaseExponent decimalSuffix = suffixToDecimal.get(suffix); | ||
| if (decimalSuffix != null) { | ||
| return decimalSuffix; | ||
| } | ||
|
|
||
| final BaseExponent binarySuffix = suffixToBinary.get(suffix); | ||
| if (binarySuffix != null) { | ||
| return binarySuffix; | ||
| } | ||
|
|
||
| if (suffix.length() > 0 && (suffix.charAt(0) == 'E' || suffix.charAt(0) == 'e')) { | ||
| return extractDecimalExponent(suffix); | ||
| } | ||
|
|
||
| throw new QuantityFormatException("Could not parse suffix"); | ||
| } | ||
|
|
||
| private BaseExponent extractDecimalExponent(String suffix) { | ||
| try { | ||
| final int exponent = Integer.parseInt(suffix.substring(1)); | ||
| return new BaseExponent(10, exponent, Quantity.Format.DECIMAL_EXPONENT); | ||
| } catch (final NumberFormatException e) { | ||
| throw new QuantityFormatException("Can't parse decimal exponent from " + suffix.substring(1)); | ||
| } | ||
| } | ||
|
|
||
| public String format(final Quantity.Format format, final int exponent) { | ||
| switch (format) { | ||
| case DECIMAL_SI: | ||
| return getDecimalSiSuffix(exponent); | ||
| case BINARY_SI: | ||
| return getBinarySiSuffix(exponent); | ||
| case DECIMAL_EXPONENT: | ||
| return exponent == 0 ? "" : "e" + exponent; | ||
| default: | ||
| throw new IllegalStateException("Can't format " + format + " with exponent " + exponent); | ||
| } | ||
| } | ||
|
|
||
| private String getBinarySiSuffix(int exponent) { | ||
| final String suffix = binaryToSuffix.get(new BaseExponent(2, exponent, Quantity.Format.BINARY_SI)); | ||
| if (suffix == null) { | ||
| throw new IllegalArgumentException("No suffix for exponent" + exponent); | ||
| } | ||
| return suffix; | ||
| } | ||
|
|
||
| private String getDecimalSiSuffix(int exponent) { | ||
| final String suffix = decimalToSuffix.get(new BaseExponent(10, exponent, Quantity.Format.DECIMAL_SI)); | ||
| if (suffix == null) { | ||
| throw new IllegalArgumentException("No suffix for exponent" + exponent); | ||
| } | ||
| return suffix; | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JavaDoc?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to add JavaDoc - to clarify, this is an across the board ask rather than just for
BaseExponent?