graphenej/graphenej/src/main/java/cy/agorise/graphenej/AssetAmount.java

227 lines
8.6 KiB
Java

package cy.agorise.graphenej;
import com.google.common.math.DoubleMath;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedLong;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.RoundingMode;
import cy.agorise.graphenej.errors.IncompatibleOperation;
import cy.agorise.graphenej.interfaces.ByteSerializable;
import cy.agorise.graphenej.interfaces.JsonSerializable;
/**
* Class used to represent a specific amount of a certain asset
*/
public class AssetAmount implements ByteSerializable, JsonSerializable {
/**
* Constants used in the JSON serialization procedure.
*/
public static final String KEY_AMOUNT = "amount";
public static final String KEY_ASSET_ID = "asset_id";
private UnsignedLong amount;
private Asset asset;
/**
* Class constructor
* @param amount The amount
* @param asset The asset
*/
public AssetAmount(UnsignedLong amount, Asset asset){
this.amount = amount;
this.asset = asset;
}
/**
* Copy constructor
* @param assetAmount The other instance
*/
public AssetAmount(AssetAmount assetAmount){
this.amount = UnsignedLong.valueOf(assetAmount.getAmount().toString());
this.asset = new Asset(assetAmount.getAsset());
}
/**
* Adds two asset amounts. They must refer to the same Asset type.
* @param other: The other AssetAmount to add to this.
* @return: A new instance of the AssetAmount class with the combined amount.
*/
public AssetAmount add(AssetAmount other){
if(!this.getAsset().getObjectId().equals(other.getAsset().getObjectId())){
throw new IncompatibleOperation("Cannot add two AssetAmount instances that refer to different assets");
}
UnsignedLong combined = this.amount.plus(other.getAmount());
return new AssetAmount(combined, asset);
}
/**
* Adds an aditional amount of base units to this AssetAmount.
* @param additional: The amount to add.
* @return: A new instance of the AssetAmount class with the added aditional.
*/
public AssetAmount add(long additional){
UnsignedLong combined = this.amount.plus(UnsignedLong.valueOf(additional));
return new AssetAmount(combined, asset);
}
/**
* Subtracts another instance of AssetAmount from this one. This method will always
* return absolute values.
* @param other: The other asset amount to subtract from this.
* @return: The absolute value of the subtraction of the other minus this asset amount.
*/
public AssetAmount subtract(AssetAmount other){
if(!this.getAsset().getObjectId().equals(other.getAsset().getObjectId())){
throw new IncompatibleOperation("Cannot subtract two AssetAmount instances that refer to different assets");
}
UnsignedLong result = null;
if(this.amount.compareTo(other.getAmount()) < 0){
result = other.getAmount().minus(this.amount);
}else{
result = this.amount.minus(other.getAmount());
}
return new AssetAmount(result, asset);
}
/**
* Multiplies the current amount by a factor provided as the first parameter. The second parameter
* specifies the rounding method to be used.
* @param factor: The multiplying factor
* @param roundingMode: The rounding mode as an instance of the {@link RoundingMode} class
* @return The same AssetAmount instance, but with the changed amount value.
*/
public AssetAmount multiplyBy(double factor, RoundingMode roundingMode){
BigDecimal originalAmount = new BigDecimal(amount.bigIntegerValue());
BigDecimal decimalResult = originalAmount.multiply(new BigDecimal(factor));
UnsignedLong resultingAmount = UnsignedLong.valueOf(DoubleMath.roundToBigInteger(decimalResult.doubleValue(), roundingMode));
return new AssetAmount(resultingAmount, new Asset(asset));
}
/**
* Multiplies the current amount by a factor, using the {@link RoundingMode#HALF_DOWN} constant.
* @param factor: The multiplying factor
* @return The same AssetAmount instance, but with the changed amount value.
*/
public AssetAmount multiplyBy(double factor){
return this.multiplyBy(factor, RoundingMode.HALF_DOWN);
}
/**
* Divides the current amount by a divisor provided as the first parameter. The second parameter
* specifies the rounding method to be used.
* @param divisor: The divisor
* @return: The same AssetAMount instance, but with the divided amount value
*/
public AssetAmount divideBy(double divisor, RoundingMode roundingMode){
BigDecimal originalAmount = new BigDecimal(amount.bigIntegerValue());
BigDecimal decimalAmount = originalAmount.divide(new BigDecimal(divisor), 18, RoundingMode.HALF_UP);
UnsignedLong resultingAmount = UnsignedLong.valueOf(DoubleMath.roundToBigInteger(decimalAmount.doubleValue(), roundingMode));
return new AssetAmount(resultingAmount, new Asset(asset));
}
/**
* Divides the current amount by a divisor provided as the first parameter, using
* the {@link RoundingMode#HALF_DOWN} constant
* @param divisor: The divisor
* @return: The same AssetAMount instance, but with the divided amount value
*/
public AssetAmount divideBy(double divisor){
return this.divideBy(divisor, RoundingMode.HALF_DOWN);
}
public void setAmount(UnsignedLong amount){
this.amount = amount;
}
public UnsignedLong getAmount(){
return this.amount;
}
public Asset getAsset(){ return this.asset; }
public void setAsset(Asset asset){
this.asset = asset;
}
@Override
public byte[] toBytes() {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DataOutput out = new DataOutputStream(byteArrayOutputStream);
try {
Varint.writeUnsignedVarLong(asset.instance, out);
} catch (IOException e) {
e.printStackTrace();
}
// Getting asset id
byte[] assetId = byteArrayOutputStream.toByteArray();
byte[] value = Util.revertLong(this.amount.longValue());
// Concatenating and returning value + asset id
return Bytes.concat(value, assetId);
}
@Override
public String toJsonString() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmountSerializer());
return gsonBuilder.create().toJson(this);
}
@Override
public JsonObject toJsonObject() {
JsonObject jsonAmount = new JsonObject();
jsonAmount.addProperty(KEY_AMOUNT, amount);
jsonAmount.addProperty(KEY_ASSET_ID, asset.getObjectId());
return jsonAmount;
}
@Override
public String toString() {
return String.format("(asset=%s, amount=%s)", asset.getObjectId(), amount.toString(10));
}
/**
* Custom serializer used to translate this object into the JSON-formatted entry we need for a transaction.
*/
public static class AssetAmountSerializer implements JsonSerializer<AssetAmount> {
@Override
public JsonElement serialize(AssetAmount assetAmount, Type type, JsonSerializationContext jsonSerializationContext) {
JsonObject obj = new JsonObject();
obj.addProperty(KEY_AMOUNT, assetAmount.amount);
obj.addProperty(KEY_ASSET_ID, assetAmount.asset.getObjectId());
return obj;
}
}
/**
* Custom deserializer used for this class
*/
public static class AssetAmountDeserializer implements JsonDeserializer<AssetAmount> {
@Override
public AssetAmount deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
Long amount = json.getAsJsonObject().get(KEY_AMOUNT).getAsLong();
String assetId = json.getAsJsonObject().get(KEY_ASSET_ID).getAsString();
AssetAmount assetAmount = new AssetAmount(UnsignedLong.valueOf(amount), new Asset(assetId));
return assetAmount;
}
}
}