Skip to content

Commit

Permalink
Merge pull request #129 from lewisheadden/AddQuantity
Browse files Browse the repository at this point in the history
Add Quantity type
  • Loading branch information
brendandburns authored Nov 29, 2017
2 parents 562b3c0 + cb1bfc2 commit 1e3fb8a
Show file tree
Hide file tree
Showing 13 changed files with 712 additions and 154 deletions.
4 changes: 4 additions & 0 deletions kubernetes/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@
<artifactId>joda-time</artifactId>
<version>${jodatime-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>junit</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.kubernetes.client.custom;

public class BaseExponent {

private final int base;
private final int exponent;
private final Quantity.Format format;

public BaseExponent(final int base, final int exponent, final 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;

return base == that.base && exponent == that.exponent && format == that.format;
}

@Override
public int hashCode() {
return this.toString().hashCode();
}
}
71 changes: 71 additions & 0 deletions kubernetes/src/main/java/io/kubernetes/client/custom/Quantity.java
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 getNumber() {
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,92 @@
package io.kubernetes.client.custom;

import org.apache.commons.lang3.tuple.Pair;

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.getNumber(), 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.getNumber().scale() > 0;
}

private String toBase1024String(final Quantity quantity) {
final BigDecimal amount = quantity.getNumber();
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.getNumber();
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 Pair.of(mantissa * 10, exponent - 1);
} else if (exponentRemainder == -1 || exponentRemainder == 2) {
return Pair.of(mantissa * 100, exponent - 2);
} else {
return Pair.of(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 Pair.of(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;
}

}
Loading

0 comments on commit 1e3fb8a

Please sign in to comment.