ContractInvocationInfoExtractor.java

/*
 * @copyright defined in LICENSE.txt
 */

package hera.spec.transaction;

import static hera.util.ValidationUtils.assertEquals;
import static hera.util.ValidationUtils.assertTrue;
import static org.slf4j.LoggerFactory.getLogger;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.NumericNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.fasterxml.jackson.databind.node.ValueNode;
import hera.api.model.BigNumber;
import hera.api.model.ContractAddress;
import hera.api.model.ContractFunction;
import hera.api.model.ContractInvocation;
import hera.api.model.Transaction;
import hera.exception.HerajException;
import hera.spec.AergoSpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.slf4j.Logger;

@NoArgsConstructor(access = AccessLevel.PACKAGE)
public class ContractInvocationInfoExtractor
    implements TransactionInfoExtractor<ContractInvocation> {

  protected static final ObjectReader reader = new ObjectMapper().reader();

  protected final Logger logger = getLogger(getClass());

  @Override
  public ContractInvocation extract(final Transaction transaction) {
    try {
      final JsonNode jsonNode = reader.readTree(transaction.getPayload().getInputStream());
      final JsonNode nameNode = jsonNode.findValue("Name");
      final JsonNode argsNode = jsonNode.findValue("Args");

      final ContractFunction function = parseToContractFunction(nameNode);
      final List<Object> args = parseToList(argsNode);
      final ContractInvocation recovered = ContractInvocation.newBuilder()
          .address(transaction.getRecipient().adapt(ContractAddress.class))
          .function(function)
          .amount(transaction.getAmount())
          .args(args)
          .build();
      return recovered;
    } catch (Exception e) {
      throw new HerajException(e);
    }
  }

  protected ContractFunction parseToContractFunction(final JsonNode jsonNode) {
    final String functionName = jsonNode.asText();
    return new ContractFunction(functionName);
  }

  protected List<Object> parseToList(final JsonNode jsonNode) {
    assertTrue(jsonNode instanceof ArrayNode);

    final List<Object> ret = new ArrayList<>();
    final int size = jsonNode.size();
    for (int i = 0; i < size; ++i) {
      final JsonNode next = jsonNode.get(i);
      Object parsedValue = null;
      if (next instanceof ArrayNode) {
        parsedValue = parseToList((ArrayNode) next);
      } else if (next instanceof ObjectNode) {
        if (null != next.get(AergoSpec.BIGNUM_JSON_KEY)) {
          parsedValue = parseToBigNumber((ObjectNode) next);
        } else {
          parsedValue = parseToMap((ObjectNode) next);
        }
      } else {
        parsedValue = parseValue((ValueNode) next);
      }
      ret.add(parsedValue);
    }
    return ret;
  }

  protected Map<String, Object> parseToMap(final ObjectNode jsonNode) {
    assertTrue(jsonNode instanceof ObjectNode);

    final Map<String, Object> ret = new HashMap<>();
    final Iterator<Entry<String, JsonNode>> it = jsonNode.fields();
    while (it.hasNext()) {
      final Entry<String, JsonNode> nextObjectNode = it.next();
      final String key = nextObjectNode.getKey();
      final JsonNode value = nextObjectNode.getValue();
      Object parsedValue = null;
      if (value instanceof ArrayNode) {
        parsedValue = parseToList((ArrayNode) value);
      } else if (value instanceof ObjectNode) {
        if (null != value.get(AergoSpec.BIGNUM_JSON_KEY)) {
          parsedValue = parseToBigNumber((ObjectNode) value);
        } else {
          parsedValue = parseToMap((ObjectNode) value);
        }
      } else {
        parsedValue = parseValue((ValueNode) value);
      }
      ret.put(key, parsedValue);
    }
    return ret;
  }

  protected BigNumber parseToBigNumber(final ObjectNode jsonNode) {
    assertTrue(jsonNode instanceof ObjectNode);
    assertEquals(1, jsonNode.size());

    final JsonNode bigNumberValueNode = jsonNode.get(AergoSpec.BIGNUM_JSON_KEY);
    if (null == bigNumberValueNode) {
      throw new IllegalArgumentException("JsonNode is not bignum value: " + jsonNode.toString());
    }

    if (!(bigNumberValueNode instanceof TextNode)) {
      throw new IllegalArgumentException(
          "JsonNode value is not bignum value: " + bigNumberValueNode.toString());
    }

    return new BigNumber(bigNumberValueNode.asText());
  }

  protected Object parseValue(final ValueNode valueNode) {
    if (valueNode instanceof BooleanNode) {
      return parseToBoolean(valueNode);
    } else if (valueNode instanceof NullNode) {
      return parseToNull(valueNode);
    } else if (valueNode instanceof NumericNode) {
      return parseToNumber(valueNode);
    } else if (valueNode instanceof TextNode) {
      return parseToString(valueNode);
    } else {
      throw new UnsupportedOperationException(
          "Unsupported node type: " + valueNode.getClass().getName());
    }
  }

  protected String parseToString(final JsonNode jsonNode) {
    return jsonNode.asText();
  }

  protected Number parseToNumber(final JsonNode jsonNode) {
    return ((NumericNode) jsonNode).numberValue();
  }

  protected Boolean parseToBoolean(final JsonNode jsonNode) {
    return jsonNode.asBoolean();
  }

  protected String parseToNull(final JsonNode jsonNode) {
    return null;
  }

}