Skip to content

Commit 1e3fb8a

Browse files
Merge pull request #129 from lewisheadden/AddQuantity
Add Quantity type
2 parents 562b3c0 + cb1bfc2 commit 1e3fb8a

File tree

13 files changed

+712
-154
lines changed

13 files changed

+712
-154
lines changed

kubernetes/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@
153153
<artifactId>joda-time</artifactId>
154154
<version>${jodatime-version}</version>
155155
</dependency>
156+
<dependency>
157+
<groupId>org.apache.commons</groupId>
158+
<artifactId>commons-lang3</artifactId>
159+
</dependency>
156160
<!-- test dependencies -->
157161
<dependency>
158162
<groupId>junit</groupId>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.kubernetes.client.custom;
2+
3+
public class BaseExponent {
4+
5+
private final int base;
6+
private final int exponent;
7+
private final Quantity.Format format;
8+
9+
public BaseExponent(final int base, final int exponent, final Quantity.Format format) {
10+
this.base = base;
11+
this.exponent = exponent;
12+
this.format = format;
13+
}
14+
15+
public int getBase() {
16+
return base;
17+
}
18+
19+
public int getExponent() {
20+
return exponent;
21+
}
22+
23+
public Quantity.Format getFormat() {
24+
return format;
25+
}
26+
27+
@Override
28+
public String toString() {
29+
return "BaseExponent{" +
30+
"base=" + base +
31+
", exponent=" + exponent +
32+
", format=" + format +
33+
'}';
34+
}
35+
36+
@Override
37+
public boolean equals(Object o) {
38+
if (this == o) return true;
39+
if (o == null || getClass() != o.getClass()) return false;
40+
41+
BaseExponent that = (BaseExponent) o;
42+
43+
return base == that.base && exponent == that.exponent && format == that.format;
44+
}
45+
46+
@Override
47+
public int hashCode() {
48+
return this.toString().hashCode();
49+
}
50+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package io.kubernetes.client.custom;
2+
3+
import com.google.gson.TypeAdapter;
4+
import com.google.gson.annotations.JsonAdapter;
5+
import com.google.gson.stream.JsonReader;
6+
import com.google.gson.stream.JsonWriter;
7+
8+
import java.io.IOException;
9+
import java.math.BigDecimal;
10+
11+
@JsonAdapter(Quantity.QuantityAdapter.class)
12+
public class Quantity {
13+
14+
private final BigDecimal number;
15+
private Format format;
16+
17+
public enum Format {
18+
DECIMAL_EXPONENT(10), DECIMAL_SI(10), BINARY_SI(2);
19+
20+
private int base;
21+
22+
Format(final int base) {
23+
this.base = base;
24+
}
25+
26+
public int getBase() {
27+
return base;
28+
}
29+
}
30+
31+
public Quantity(final BigDecimal number, final Format format) {
32+
this.number = number;
33+
this.format = format;
34+
}
35+
36+
public BigDecimal getNumber() {
37+
return number;
38+
}
39+
40+
public Format getFormat() {
41+
return format;
42+
}
43+
44+
public static Quantity fromString(final String value) {
45+
return new QuantityFormatter().parse(value);
46+
}
47+
48+
public String toSuffixedString() {
49+
return new QuantityFormatter().format(this);
50+
}
51+
52+
@Override
53+
public String toString() {
54+
return "Quantity{" +
55+
"number=" + number +
56+
", format=" + format +
57+
'}';
58+
}
59+
60+
public class QuantityAdapter extends TypeAdapter<Quantity> {
61+
@Override
62+
public void write(JsonWriter jsonWriter, Quantity quantity) throws IOException {
63+
jsonWriter.value(quantity.toSuffixedString());
64+
}
65+
66+
@Override
67+
public Quantity read(JsonReader jsonReader) throws IOException {
68+
return Quantity.fromString(jsonReader.nextString());
69+
}
70+
}
71+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.kubernetes.client.custom;
2+
3+
public class QuantityFormatException extends RuntimeException {
4+
public QuantityFormatException(String s) {
5+
super(s);
6+
}
7+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package io.kubernetes.client.custom;
2+
3+
import org.apache.commons.lang3.tuple.Pair;
4+
5+
import java.math.BigDecimal;
6+
import java.math.MathContext;
7+
8+
public class QuantityFormatter {
9+
10+
private static final String PARTS_RE = "[eEinumkKMGTP]+";
11+
12+
public Quantity parse(final String value) {
13+
if (value == null || value.isEmpty()) {
14+
throw new QuantityFormatException("");
15+
}
16+
final String[] parts = value.split(PARTS_RE);
17+
final BigDecimal numericValue = parseNumericValue(parts[0]);
18+
final String suffix = value.substring(parts[0].length());
19+
final BaseExponent baseExponent = new SuffixFormatter().parse(suffix);
20+
final BigDecimal unitMultiplier = BigDecimal.valueOf(baseExponent.getBase()).pow(baseExponent.getExponent(), MathContext.DECIMAL64);
21+
final BigDecimal unitlessValue = numericValue.multiply(unitMultiplier);
22+
return new Quantity(unitlessValue, baseExponent.getFormat());
23+
}
24+
25+
private static BigDecimal parseNumericValue(String part) {
26+
try {
27+
return new BigDecimal(part);
28+
} catch (final NumberFormatException e) {
29+
throw new QuantityFormatException("Unable to parse numeric part of quantity: " + part);
30+
}
31+
}
32+
33+
public String format(final Quantity quantity) {
34+
switch (quantity.getFormat()) {
35+
case DECIMAL_SI:
36+
case DECIMAL_EXPONENT:
37+
return toBase10String(quantity);
38+
case BINARY_SI:
39+
if (isFractional(quantity)) {
40+
return toBase10String(new Quantity(quantity.getNumber(), Quantity.Format.DECIMAL_SI));
41+
}
42+
return toBase1024String(quantity);
43+
default:
44+
throw new IllegalArgumentException("Can't format a " + quantity.getFormat() + " quantity");
45+
}
46+
}
47+
48+
private boolean isFractional(Quantity quantity) {
49+
return quantity.getNumber().scale() > 0;
50+
}
51+
52+
private String toBase1024String(final Quantity quantity) {
53+
final BigDecimal amount = quantity.getNumber();
54+
final long value = amount.unscaledValue().longValue();
55+
final int exponent = -amount.scale();
56+
final Pair<Long, Integer> resultAndTimes = removeFactorsForBase(value, 1024);
57+
return resultAndTimes.getLeft() + new SuffixFormatter().format(quantity.getFormat(), exponent + resultAndTimes.getRight() * 10);
58+
}
59+
60+
private String toBase10String(final Quantity quantity) {
61+
final BigDecimal amount = quantity.getNumber();
62+
final long value = amount.unscaledValue().longValue();
63+
final int exponent = -amount.scale();
64+
final Pair<Long, Integer> resultAndTimes = removeFactorsForBase(value, 10);
65+
final int postFactoringExponent = exponent + resultAndTimes.getRight();
66+
final Pair<Long, Integer> valueAndExponent = ensureExponentIsMultipleOf3(resultAndTimes.getLeft(), postFactoringExponent);
67+
return valueAndExponent.getLeft() + new SuffixFormatter().format(quantity.getFormat(), valueAndExponent.getRight());
68+
}
69+
70+
private Pair<Long, Integer> ensureExponentIsMultipleOf3(final long mantissa, final int exponent) {
71+
final long exponentRemainder = exponent % 3;
72+
if (exponentRemainder == 1 || exponentRemainder == -2) {
73+
return Pair.of(mantissa * 10, exponent - 1);
74+
} else if (exponentRemainder == -1 || exponentRemainder == 2) {
75+
return Pair.of(mantissa * 100, exponent - 2);
76+
} else {
77+
return Pair.of(mantissa, exponent);
78+
}
79+
}
80+
81+
private Pair<Long, Integer> removeFactorsForBase(final long value, final int base) {
82+
int times = 0;
83+
long result = value;
84+
while (result >= base && result % base == 0) {
85+
times++;
86+
result = result / base;
87+
}
88+
return Pair.of(result, times);
89+
}
90+
91+
}
92+
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package io.kubernetes.client.custom;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
public class SuffixFormatter {
7+
8+
private static final Map<String, BaseExponent> suffixToBinary = new HashMap<String, BaseExponent>() {
9+
{
10+
put("", new BaseExponent(2, 0, Quantity.Format.BINARY_SI));
11+
put("Ki", new BaseExponent(2, 10, Quantity.Format.BINARY_SI));
12+
put("Mi", new BaseExponent(2, 20, Quantity.Format.BINARY_SI));
13+
put("Gi", new BaseExponent(2, 30, Quantity.Format.BINARY_SI));
14+
put("Ti", new BaseExponent(2, 40, Quantity.Format.BINARY_SI));
15+
put("Pi", new BaseExponent(2, 50, Quantity.Format.BINARY_SI));
16+
put("Ei", new BaseExponent(2, 60, Quantity.Format.BINARY_SI));
17+
}
18+
};
19+
20+
private static final Map<String, BaseExponent> suffixToDecimal = new HashMap<String, BaseExponent>() {
21+
{
22+
put("n", new BaseExponent(10, -9, Quantity.Format.DECIMAL_SI));
23+
put("u", new BaseExponent(10, -6, Quantity.Format.DECIMAL_SI));
24+
put("m", new BaseExponent(10, -3, Quantity.Format.DECIMAL_SI));
25+
put("", new BaseExponent(10, 0, Quantity.Format.DECIMAL_SI));
26+
put("k", new BaseExponent(10, 3, Quantity.Format.DECIMAL_SI));
27+
put("M", new BaseExponent(10, 6, Quantity.Format.DECIMAL_SI));
28+
put("G", new BaseExponent(10, 9, Quantity.Format.DECIMAL_SI));
29+
put("T", new BaseExponent(10, 12, Quantity.Format.DECIMAL_SI));
30+
put("P", new BaseExponent(10, 15, Quantity.Format.DECIMAL_SI));
31+
put("E", new BaseExponent(10, 18, Quantity.Format.DECIMAL_SI));
32+
}
33+
};
34+
35+
private static final Map<BaseExponent, String> decimalToSuffix = new HashMap<BaseExponent, String>() {
36+
{
37+
for (Entry<String, BaseExponent> entry : suffixToDecimal.entrySet()) {
38+
put(entry.getValue(), entry.getKey());
39+
}
40+
}
41+
};
42+
43+
private static final Map<BaseExponent, String> binaryToSuffix = new HashMap<BaseExponent, String>() {
44+
{
45+
for (Entry<String, BaseExponent> entry : suffixToBinary.entrySet()) {
46+
put(entry.getValue(), entry.getKey());
47+
}
48+
}
49+
};
50+
51+
public BaseExponent parse(final String suffix) {
52+
final BaseExponent decimalSuffix = suffixToDecimal.get(suffix);
53+
if (decimalSuffix != null) {
54+
return decimalSuffix;
55+
}
56+
57+
final BaseExponent binarySuffix = suffixToBinary.get(suffix);
58+
if (binarySuffix != null) {
59+
return binarySuffix;
60+
}
61+
62+
if (suffix.length() > 0 && (suffix.charAt(0) == 'E' || suffix.charAt(0) == 'e')) {
63+
return extractDecimalExponent(suffix);
64+
}
65+
66+
throw new QuantityFormatException("Could not parse suffix");
67+
}
68+
69+
private BaseExponent extractDecimalExponent(String suffix) {
70+
try {
71+
final int exponent = Integer.parseInt(suffix.substring(1));
72+
return new BaseExponent(10, exponent, Quantity.Format.DECIMAL_EXPONENT);
73+
} catch (final NumberFormatException e) {
74+
throw new QuantityFormatException("Can't parse decimal exponent from " + suffix.substring(1));
75+
}
76+
}
77+
78+
public String format(final Quantity.Format format, final int exponent) {
79+
switch (format) {
80+
case DECIMAL_SI:
81+
return getDecimalSiSuffix(exponent);
82+
case BINARY_SI:
83+
return getBinarySiSuffix(exponent);
84+
case DECIMAL_EXPONENT:
85+
return exponent == 0 ? "" : "e" + exponent;
86+
default:
87+
throw new IllegalStateException("Can't format " + format + " with exponent " + exponent);
88+
}
89+
}
90+
91+
private String getBinarySiSuffix(int exponent) {
92+
final String suffix = binaryToSuffix.get(new BaseExponent(2, exponent, Quantity.Format.BINARY_SI));
93+
if (suffix == null) {
94+
throw new IllegalArgumentException("No suffix for exponent" + exponent);
95+
}
96+
return suffix;
97+
}
98+
99+
private String getDecimalSiSuffix(int exponent) {
100+
final String suffix = decimalToSuffix.get(new BaseExponent(10, exponent, Quantity.Format.DECIMAL_SI));
101+
if (suffix == null) {
102+
throw new IllegalArgumentException("No suffix for exponent" + exponent);
103+
}
104+
return suffix;
105+
}
106+
107+
}

0 commit comments

Comments
 (0)