PayloadResolver.java

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

package hera.spec.resolver;

import static hera.api.model.BytesValue.of;
import static hera.util.BytesValueUtils.trimPrefix;
import static java.util.Arrays.asList;
import static org.slf4j.LoggerFactory.getLogger;

import hera.annotation.ApiAudience;
import hera.annotation.ApiStability;
import hera.api.model.AccountAddress;
import hera.api.model.BytesValue;
import hera.api.model.ContractDefinition;
import hera.api.model.ContractInvocation;
import hera.exception.HerajException;
import hera.spec.resolver.PayloadSpec.Type;
import hera.util.LittleEndianDataOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;

@ApiAudience.Private
@ApiStability.Unstable
public class PayloadResolver {

  protected static final Logger logger = getLogger(PayloadResolver.class);

  /**
   * Resolve targets in a payload form.
   *
   * @param type a payload type
   * @param targets targets
   * @return resolved payload
   *
   * @throws HerajException if fails
   */
  public static BytesValue resolve(final Type type, final Object... targets) {
    try {
      logger.trace("Payload resolve type: {}, target size: {}", type, targets.length);
      validateResolveArgs(type, targets);
      BytesValue resolved = BytesValue.EMPTY;
      switch (type) {
        case ContractDefinition:
          resolved = resolveContractDefinition(targets);
          break;
        case ContractInvocation:
          resolved = resolveContractInvocation(targets);
          break;
        case Stake:
          resolved = resolveStake(type, targets);
          break;
        case Unstake:
          resolved = resolveUnstake(type, targets);
          break;
        case Vote:
          resolved = resolveVote(type, targets);
          break;
        case CreateName:
          resolved = resolveCreateName(type, targets);
          break;
        case UpdateName:
          resolved = resolveUpdateName(type, targets);
          break;
        default:
          resolved = of(targets.toString().getBytes());
          break;
      }
      logger.trace("Resolved payload: {}", resolved);
      return resolved;
    } catch (HerajException e) {
      throw e;
    } catch (Exception e) {
      throw new HerajException(e);
    }
  }

  protected static void validateResolveArgs(final Type type, final Object[] instances) {
    final int expectedSize = type.getTargets().length;
    if (instances.length != expectedSize) {
      throw new IllegalArgumentException("Targets length must be " + expectedSize);
    }
    for (int i = 0; i < expectedSize; ++i) {
      final Class<?> mustbe = type.getTargets()[i];
      final Object instance = instances[i];
      if (!mustbe.isInstance(instance)) {
        throw new IllegalArgumentException(
            "Target must be " + mustbe.getName() + " but was " + instance.getClass().getName());
      }
    }
  }

  protected static BytesValue resolveContractDefinition(final Object[] targets) throws IOException {
    final ContractDefinition contractDefinition = (ContractDefinition) targets[0];
    final byte[] rawPayload = trimPrefix(contractDefinition.getDecodedContract().getValue());
    final ByteArrayOutputStream rawStream = new ByteArrayOutputStream();
    final LittleEndianDataOutputStream dataOut = new LittleEndianDataOutputStream(rawStream);
    try {
      dataOut.writeInt(rawPayload.length + 4);
      dataOut.write(rawPayload);
      if (!contractDefinition.getConstructorArgs().isEmpty()) {
        final String constructorArgs =
            JsonResolver.asJsonArray(contractDefinition.getConstructorArgs());
        dataOut.write(constructorArgs.getBytes());
      }
    } finally {
      dataOut.close();
    }

    return new BytesValue(rawStream.toByteArray());
  }

  protected static BytesValue resolveContractInvocation(final Object[] targets) {
    final ContractInvocation contractInvocation = (ContractInvocation) targets[0];
    final String name = contractInvocation.getFunction().getName();
    final List<Object> args = contractInvocation.getArgs();
    final String jsonForm = asJsonForm(name, args);
    return new BytesValue(jsonForm.getBytes());
  }

  protected static BytesValue resolveStake(final Type type, final Object[] targets) {
    final String jsonForm = asJsonForm(PayloadSpec.VERSION + type.getName(), asList(targets));
    return new BytesValue(jsonForm.getBytes());
  }

  protected static BytesValue resolveUnstake(final Type type, final Object[] targets) {
    final String jsonForm = asJsonForm(PayloadSpec.VERSION + type.getName(), asList(targets));
    return new BytesValue(jsonForm.getBytes());
  }

  protected static BytesValue resolveVote(final Type type, final Object[] targets)
      throws IOException {
    final String voteId = (String) targets[0];
    final String[] candidates = (String[]) targets[1];
    final List<Object> args = new ArrayList<Object>();
    for (final String candidate : candidates) {
      args.add(candidate);
    }
    final String jsonForm = asJsonForm(PayloadSpec.VERSION + voteId, args);
    return new BytesValue(jsonForm.getBytes());
  }

  protected static BytesValue resolveCreateName(final Type type, final Object[] targets) {
    final String jsonForm = asJsonForm(PayloadSpec.VERSION + type.getName(), asList(targets));
    return new BytesValue(jsonForm.getBytes());
  }

  protected static BytesValue resolveUpdateName(final Type type, final Object[] targets)
      throws IOException {
    final String name = (String) targets[0];
    final String newOwner = ((AccountAddress) targets[1]).getEncoded();
    final List<Object> args = new ArrayList<Object>();
    args.add(name);
    args.add(newOwner);
    final String jsonForm = asJsonForm(PayloadSpec.VERSION + type.getName(), args);
    return new BytesValue(jsonForm.toString().getBytes());
  }

  protected static String asJsonForm(final String name, final List<Object> args) {
    final Map<String, Object> map = new HashMap<>();
    map.put("Name", name);
    map.put("Args", args);
    return JsonResolver.asJsonObject(map);
  }

}