All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cryptape.cita.codegen.SolidityFunctionWrapper Maven / Gradle / Ivy

The newest version!
package com.cryptape.cita.codegen;

import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.lang.model.element.Modifier;

import com.cryptape.cita.protocol.CITAj;
import com.cryptape.cita.protocol.ObjectMapperFactory;
import com.cryptape.cita.protocol.core.DefaultBlockParameter;
import com.cryptape.cita.protocol.core.RemoteCall;
import com.cryptape.cita.protocol.core.methods.request.AppFilter;
import com.cryptape.cita.protocol.core.methods.response.Log;
import com.cryptape.cita.protocol.core.methods.response.TransactionReceipt;
import com.cryptape.cita.utils.Collection;
import com.cryptape.cita.utils.Version;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;

import io.reactivex.Flowable;
import com.cryptape.cita.abi.EventEncoder;
import com.cryptape.cita.abi.EventValues;
import com.cryptape.cita.abi.FunctionEncoder;
import com.cryptape.cita.abi.TypeReference;
import com.cryptape.cita.abi.datatypes.Address;
import com.cryptape.cita.abi.datatypes.Bool;
import com.cryptape.cita.abi.datatypes.DynamicArray;
import com.cryptape.cita.abi.datatypes.DynamicBytes;
import com.cryptape.cita.abi.datatypes.Event;
import com.cryptape.cita.abi.datatypes.Function;
import com.cryptape.cita.abi.datatypes.StaticArray;
import com.cryptape.cita.abi.datatypes.Type;
import com.cryptape.cita.abi.datatypes.Utf8String;
import com.cryptape.cita.abi.datatypes.generated.AbiTypes;
import com.cryptape.cita.protocol.core.methods.response.AbiDefinition;
import com.cryptape.cita.tx.Contract;
import com.cryptape.cita.tx.TransactionManager;
import com.cryptape.cita.utils.Strings;

/**
 * Generate Java Classes based on generated Solidity bin and abi files.
 */
public class SolidityFunctionWrapper extends Generator {

    private static final String BINARY = "BINARY";
    private static final String ABI = "ABI";
    private static final String CITAJ = "citaj";
    private static final String CREDENTIALS = "credentials";
    private static final String TRANSACTION_MANAGER = "transactionManager";
    private static final String INITIAL_VALUE = "initialWeiValue";
    private static final String CONTRACT_ADDRESS = "contractAddress";
    private static final String GAS_PRICE = "gasPrice";
    private static final String GAS_LIMIT = "gasLimit";
    private static final String START_BLOCK = "startBlock";
    private static final String END_BLOCK = "endBlock";
    private static final String WEI_VALUE = "weiValue";

    // adapt to cita
    private static final String QUOTA = "quota";
    private static final String NONCE = "nonce";
    private static final String VALID_UNTIL_BLOCK = "validUntilBlock";
    private static final String VERSION = "version";
    private static final String CHAIN_ID = "chainId";
    private static final String VALUE = "value";

    private static final String CODEGEN_WARNING = "

Auto generated code.\n" + "

Do not modify!\n" + "

Please use the " + "" + "codegen module to update.\n"; private final boolean useNativeJavaTypes; public SolidityFunctionWrapper(boolean useNativeJavaTypes) { this.useNativeJavaTypes = useNativeJavaTypes; } @SuppressWarnings("unchecked") public void generateJavaFiles( String contractName, String bin, String abi, String destinationDir, String basePackageName) throws IOException, ClassNotFoundException { generateJavaFiles(contractName, bin, loadContractDefinition(abi), destinationDir, basePackageName, null); } void generateJavaFiles( String contractName, String bin, List abi, String destinationDir, String basePackageName, Map addresses) throws IOException, ClassNotFoundException { String className = Strings.capitaliseFirstLetter(contractName); TypeSpec.Builder classBuilder = createClassBuilder(className, bin, convertToAbiString(abi)); classBuilder.addMethod( buildConstructorAdaptToCita(TransactionManager.class, TRANSACTION_MANAGER)); classBuilder.addMethods( buildFunctionDefinitions(className, classBuilder, abi)); classBuilder.addMethod( buildLoadAdaptToCita(className, TransactionManager.class, TRANSACTION_MANAGER)); addAddressesSupport(classBuilder, addresses); write(basePackageName, classBuilder.build(), destinationDir); } private void addAddressesSupport(TypeSpec.Builder classBuilder, Map addresses) { if (addresses != null) { ClassName stringType = ClassName.get(String.class); ClassName mapType = ClassName.get(HashMap.class); TypeName mapStringString = ParameterizedTypeName.get(mapType, stringType, stringType); FieldSpec addressesStaticField = FieldSpec .builder(mapStringString, "_addresses", Modifier.PROTECTED, Modifier.STATIC, Modifier.FINAL) .build(); classBuilder.addField(addressesStaticField); final CodeBlock.Builder staticInit = CodeBlock.builder(); staticInit.addStatement("_addresses = new HashMap<>()"); addresses.forEach((k, v) -> staticInit.addStatement(String.format("_addresses.put(\"%1s\", \"%2s\")", k, v)) ); classBuilder.addStaticBlock(staticInit.build()); // See org.citaj.tx.Contract#getStaticDeployedAddress(String) MethodSpec getAddress = MethodSpec .methodBuilder("getStaticDeployedAddress") .addModifiers(Modifier.PROTECTED) .returns(stringType) .addParameter(stringType, "networkId") .addCode( CodeBlock .builder() .addStatement("return _addresses.get(networkId)") .build()) .build(); classBuilder.addMethod(getAddress); MethodSpec getPreviousAddress = MethodSpec .methodBuilder("getPreviouslyDeployedAddress") .addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.STATIC) .returns(stringType) .addParameter(stringType, "networkId") .addCode( CodeBlock .builder() .addStatement("return _addresses.get(networkId)") .build()) .build(); classBuilder.addMethod(getPreviousAddress); } } private TypeSpec.Builder createClassBuilder(String className, String binary, String abi) { String javadoc = CODEGEN_WARNING + getCITAjVersion(); return TypeSpec.classBuilder(className) .addModifiers(Modifier.PUBLIC) .addJavadoc(javadoc) .superclass(Contract.class) .addField(createBinaryDefinition(binary)) .addField(createAbiDefinition(abi)); } private String getCITAjVersion() { String version; try { // This only works if run as part of the citaj command line tools which contains // a version.properties file version = Version.getVersion(); } catch (IOException | NullPointerException e) { version = Version.DEFAULT; } return "\n

Generated with citaj version " + version + ".\n"; } private FieldSpec createBinaryDefinition(String binary) { return FieldSpec.builder(String.class, BINARY) .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) .initializer("$S", binary) .build(); } private FieldSpec createAbiDefinition(String abi) { return FieldSpec.builder(String.class, ABI) .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) .initializer("$S", abi) .build(); } private List buildFunctionDefinitions( String className, TypeSpec.Builder classBuilder, List functionDefinitions) throws ClassNotFoundException { List methodSpecs = new ArrayList<>(); boolean constructor = false; for (AbiDefinition functionDefinition : functionDefinitions) { if (functionDefinition.getType().equals("function")) { methodSpecs.add(buildFunction(functionDefinition)); } else if (functionDefinition.getType().equals("event")) { buildEventFunctions(functionDefinition, classBuilder); } else if (functionDefinition.getType().equals("constructor")) { constructor = true; // methodSpecs.add(buildDeploy( // className, functionDefinition, Credentials.class, CREDENTIALS)); // methodSpecs.add(buildDeploy( // className, functionDefinition, TransactionManager.class, // TRANSACTION_MANAGER)); // adapt to cita methodSpecs.add(buildDeployAdaptToCita( className, functionDefinition, TransactionManager.class, TRANSACTION_MANAGER)); } } // constructor will not be specified in ABI file if its empty if (!constructor) { // MethodSpec.Builder credentialsMethodBuilder = // getDeployMethodSpec(className, Credentials.class, CREDENTIALS, false); // methodSpecs.add(buildDeployNoParams( // credentialsMethodBuilder, className, CREDENTIALS, false)); // MethodSpec.Builder transactionManagerMethodBuilder = // getDeployMethodSpec( // className, TransactionManager.class, TRANSACTION_MANAGER, false); // methodSpecs.add(buildDeployNoParams( // transactionManagerMethodBuilder, className, TRANSACTION_MANAGER, false)); MethodSpec.Builder txManagerMethodBuilderAdapter = getDeployMethodSpec(className, TransactionManager.class, TRANSACTION_MANAGER); methodSpecs.add(buildDeployNoParams( txManagerMethodBuilderAdapter, className, TRANSACTION_MANAGER)); } return methodSpecs; } private static MethodSpec buildConstructor(Class authType, String authName) { return MethodSpec.constructorBuilder() .addModifiers(Modifier.PROTECTED) .addParameter(String.class, CONTRACT_ADDRESS) .addParameter(CITAj.class, CITAJ) .addParameter(authType, authName) .addParameter(BigInteger.class, GAS_PRICE) .addParameter(BigInteger.class, GAS_LIMIT) .addStatement("super($N, $N, $N, $N, $N, $N)", BINARY, CONTRACT_ADDRESS, CITAJ, authName, GAS_PRICE, GAS_LIMIT) .build(); } private static MethodSpec buildConstructorAdaptToCita(Class authType, String authName) { return MethodSpec.constructorBuilder() .addModifiers(Modifier.PROTECTED) .addParameter(String.class, CONTRACT_ADDRESS) .addParameter(CITAj.class, CITAJ) .addParameter(authType, authName) .addStatement("super($N, $N, $N, $N)", BINARY, CONTRACT_ADDRESS, CITAJ, authName) .build(); } private MethodSpec buildDeploy( String className, AbiDefinition functionDefinition, Class authType, String authName) { boolean isPayable = functionDefinition.isPayable(); MethodSpec.Builder methodBuilder = getDeployMethodSpec( className, authType, authName, isPayable); String inputParams = addParameters(methodBuilder, functionDefinition.getInputs()); if (!inputParams.isEmpty()) { return buildDeployWithParams( methodBuilder, className, inputParams, authName, isPayable); } else { return buildDeployNoParams(methodBuilder, className, authName, isPayable); } } private MethodSpec buildDeployAdaptToCita( String className, AbiDefinition functionDefinition, Class authType, String authName) { MethodSpec.Builder methodBuilder = getDeployMethodSpec( className, authType, authName); String inputParams = addParameters(methodBuilder, functionDefinition.getInputs()); if (!inputParams.isEmpty()) { return buildDeployWithParams( methodBuilder, className, inputParams, authName); } else { return buildDeployNoParams(methodBuilder, className, authName); } } private static MethodSpec buildDeployWithParams( MethodSpec.Builder methodBuilder, String className, String inputParams, String authName, boolean isPayable) { methodBuilder.addStatement("$T encodedConstructor = $T.encodeConstructor(" + "$T.<$T>asList($L)" + ")", String.class, FunctionEncoder.class, Arrays.class, Type.class, inputParams); if (isPayable) { methodBuilder.addStatement( "return deployRemoteCall($L.class, $L, $L, $L, $L, $L, encodedConstructor, $L)", className, CITAJ, authName, GAS_PRICE, GAS_LIMIT, BINARY, INITIAL_VALUE); } else { methodBuilder.addStatement( "return deployRemoteCall($L.class, $L, $L, $L, $L, $L, encodedConstructor)", className, CITAJ, authName, GAS_PRICE, GAS_LIMIT, BINARY); } return methodBuilder.build(); } private static MethodSpec buildDeployWithParams( MethodSpec.Builder methodBuilder, String className, String inputParams, String authName) { methodBuilder.addStatement("$T encodedConstructor = $T.encodeConstructor(" + "$T.<$T>asList($L)" + ")", String.class, FunctionEncoder.class, Arrays.class, Type.class, inputParams); methodBuilder.addStatement( "return deployRemoteCall" + "($L.class, $L, $L, $L, $L, $L, $L, $L, $L, $L, encodedConstructor)", className, CITAJ, authName, QUOTA, NONCE, VALID_UNTIL_BLOCK, VERSION, CHAIN_ID, VALUE, BINARY); return methodBuilder.build(); } private static MethodSpec buildDeployNoParams( MethodSpec.Builder methodBuilder, String className, String authName, boolean isPayable) { if (isPayable) { methodBuilder.addStatement( "return deployRemoteCall($L.class, $L, $L, $L, $L, $L, \"\", $L)", className, CITAJ, authName, GAS_PRICE, GAS_LIMIT, BINARY, INITIAL_VALUE); } else { methodBuilder.addStatement( "return deployRemoteCall($L.class, $L, $L, $L, $L, $L, \"\")", className, CITAJ, authName, GAS_PRICE, GAS_LIMIT, BINARY); } return methodBuilder.build(); } private static MethodSpec buildDeployNoParams( MethodSpec.Builder methodBuilder, String className, String authName) { methodBuilder.addStatement( "return deployRemoteCall" + "($L.class, $L, $L, $L, $L, $L, $L, $L, $L, $L, \"\")", className, CITAJ, authName, QUOTA, NONCE, VALID_UNTIL_BLOCK, VERSION, CHAIN_ID, VALUE, BINARY); return methodBuilder.build(); } private static MethodSpec.Builder getDeployMethodSpec( String className, Class authType, String authName, boolean isPayable) { MethodSpec.Builder builder = MethodSpec.methodBuilder("deploy") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns( buildRemoteCall(TypeVariableName.get(className, Type.class))) .addParameter(CITAj.class, CITAJ) .addParameter(authType, authName) .addParameter(BigInteger.class, GAS_PRICE) .addParameter(BigInteger.class, GAS_LIMIT); if (isPayable) { return builder.addParameter(BigInteger.class, INITIAL_VALUE); } else { return builder; } } // adapt to cita private static MethodSpec.Builder getDeployMethodSpec( String className, Class authType, String authName) { MethodSpec.Builder builder = MethodSpec.methodBuilder("deploy") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns( buildRemoteCall(TypeVariableName.get(className, Type.class))) .addParameter(CITAj.class, CITAJ) .addParameter(authType, authName) .addParameter(Long.class, QUOTA) .addParameter(String.class, NONCE) .addParameter(Long.class, VALID_UNTIL_BLOCK) .addParameter(Integer.class, VERSION) .addParameter(String.class, VALUE) .addParameter(BigInteger.class, CHAIN_ID); return builder; } private static MethodSpec buildLoad( String className, Class authType, String authName) { return MethodSpec.methodBuilder("load") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(TypeVariableName.get(className, Type.class)) .addParameter(String.class, CONTRACT_ADDRESS) .addParameter(CITAj.class, CITAJ) .addParameter(authType, authName) .addParameter(BigInteger.class, GAS_PRICE) .addParameter(BigInteger.class, GAS_LIMIT) .addStatement("return new $L($L, $L, $L, $L, $L)", className, CONTRACT_ADDRESS, CITAJ, authName, GAS_PRICE, GAS_LIMIT) .build(); } private static MethodSpec buildLoadAdaptToCita( String className, Class authType, String authName) { return MethodSpec.methodBuilder("load") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(TypeVariableName.get(className, Type.class)) .addParameter(String.class, CONTRACT_ADDRESS) .addParameter(CITAj.class, CITAJ) .addParameter(authType, authName) .addStatement("return new $L($L, $L, $L)", className, CONTRACT_ADDRESS, CITAJ, authName) .build(); } String addParameters( MethodSpec.Builder methodBuilder, List namedTypes) { List inputParameterTypes = buildParameterTypes(namedTypes); List nativeInputParameterTypes = new ArrayList<>(inputParameterTypes.size()); for (ParameterSpec parameterSpec:inputParameterTypes) { TypeName typeName = getWrapperType(parameterSpec.type); nativeInputParameterTypes.add( ParameterSpec.builder(typeName, parameterSpec.name).build()); } methodBuilder.addParameters(nativeInputParameterTypes); if (useNativeJavaTypes) { return Collection.join( inputParameterTypes, ", \n", // this results in fully qualified names being generated parameterSpec -> createMappedParameterTypes(parameterSpec)); } else { return Collection.join( inputParameterTypes, ", ", parameterSpec -> parameterSpec.name); } } private String createMappedParameterTypes(ParameterSpec parameterSpec) { if (parameterSpec.type instanceof ParameterizedTypeName) { List typeNames = ((ParameterizedTypeName) parameterSpec.type).typeArguments; if (typeNames.size() != 1) { throw new UnsupportedOperationException( "Only a single parameterized type is supported"); } else { TypeName typeName = typeNames.get(0); return "new " + parameterSpec.type + "(\n" + " Utils.typeMap(" + parameterSpec.name + ", " + typeName + ".class)" + ")"; } } else { return "new " + parameterSpec.type + "(" + parameterSpec.name + ")"; } } private TypeName getWrapperType(TypeName typeName) { if (useNativeJavaTypes) { return getNativeType(typeName); } else { return typeName; } } private TypeName getWrapperRawType(TypeName typeName) { if (useNativeJavaTypes) { if (typeName instanceof ParameterizedTypeName) { return ClassName.get(List.class); } return getNativeType(typeName); } else { return typeName; } } private TypeName getIndexedEventWrapperType(TypeName typeName) { if (useNativeJavaTypes) { return getEventNativeType(typeName); } else { return typeName; } } static TypeName getNativeType(TypeName typeName) { if (typeName instanceof ParameterizedTypeName) { return getNativeType((ParameterizedTypeName) typeName); } String simpleName = ((ClassName) typeName).simpleName(); if (simpleName.equals(Address.class.getSimpleName())) { return TypeName.get(String.class); } else if (simpleName.startsWith("Uint")) { return TypeName.get(BigInteger.class); } else if (simpleName.startsWith("Int")) { return TypeName.get(BigInteger.class); } else if (simpleName.equals(Utf8String.class.getSimpleName())) { return TypeName.get(String.class); } else if (simpleName.startsWith("Bytes")) { return TypeName.get(byte[].class); } else if (simpleName.equals(DynamicBytes.class.getSimpleName())) { return TypeName.get(byte[].class); } else if (simpleName.equals(Bool.class.getSimpleName())) { return TypeName.get(Boolean.class); // boolean cannot be a parameterized type } else { throw new UnsupportedOperationException( "Unsupported type: " + typeName + ", no native type mapping exists."); } } static TypeName getNativeType(ParameterizedTypeName parameterizedTypeName) { List typeNames = parameterizedTypeName.typeArguments; List nativeTypeNames = new ArrayList<>(typeNames.size()); for (TypeName enclosedTypeName : typeNames) { nativeTypeNames.add(getNativeType(enclosedTypeName)); } return ParameterizedTypeName.get( ClassName.get(List.class), nativeTypeNames.toArray(new TypeName[nativeTypeNames.size()])); } static TypeName getEventNativeType(TypeName typeName) { if (typeName instanceof ParameterizedTypeName) { return TypeName.get(byte[].class); } String simpleName = ((ClassName) typeName).simpleName(); if (simpleName.equals(Utf8String.class.getSimpleName())) { return TypeName.get(byte[].class); } else { return getNativeType(typeName); } } static List buildParameterTypes(List namedTypes) { List result = new ArrayList<>(namedTypes.size()); for (int i = 0; i < namedTypes.size(); i++) { AbiDefinition.NamedType namedType = namedTypes.get(i); String name = createValidParamName(namedType.getName(), i); String type = namedTypes.get(i).getType(); result.add(ParameterSpec.builder(buildTypeName(type), name).build()); } return result; } /** * Public Solidity arrays and maps require an unnamed input parameter - multiple if they * require a struct type. * * @param name parameter name * @param idx parameter index * @return non-empty parameter name */ static String createValidParamName(String name, int idx) { if (name.equals("")) { return "param" + idx; } else { return name; } } static List buildTypeNames(List namedTypes) { List result = new ArrayList<>(namedTypes.size()); for (AbiDefinition.NamedType namedType : namedTypes) { result.add(buildTypeName(namedType.getType())); } return result; } MethodSpec buildFunction( AbiDefinition functionDefinition) throws ClassNotFoundException { String functionName = functionDefinition.getName(); MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(functionName) .addModifiers(Modifier.PUBLIC); String inputParams = addParameters(methodBuilder, functionDefinition.getInputs()); List outputParameterTypes = buildTypeNames(functionDefinition.getOutputs()); if (functionDefinition.isConstant()) { buildConstantFunction( functionDefinition, methodBuilder, outputParameterTypes, inputParams); } else { buildTransactionFunction( functionDefinition, methodBuilder, inputParams); } return methodBuilder.build(); } private void buildConstantFunction( AbiDefinition functionDefinition, MethodSpec.Builder methodBuilder, List outputParameterTypes, String inputParams) throws ClassNotFoundException { String functionName = functionDefinition.getName(); if (outputParameterTypes.isEmpty()) { throw new RuntimeException("Only transactional methods should have void return types"); } else if (outputParameterTypes.size() == 1) { TypeName typeName = outputParameterTypes.get(0); TypeName nativeReturnTypeName; if (useNativeJavaTypes) { nativeReturnTypeName = getWrapperRawType(typeName); } else { nativeReturnTypeName = getWrapperType(typeName); } methodBuilder.returns(buildRemoteCall(nativeReturnTypeName)); methodBuilder.addStatement("$T function = " + "new $T($S, \n$T.<$T>asList($L), " + "\n$T.<$T>asList(new $T<$T>() {}))", Function.class, Function.class, functionName, Arrays.class, Type.class, inputParams, Arrays.class, TypeReference.class, TypeReference.class, typeName); if (useNativeJavaTypes) { methodBuilder.addStatement( "return executeRemoteCallSingleValueReturn(function, $T.class)", nativeReturnTypeName); } else { methodBuilder.addStatement("return executeRemoteCallSingleValueReturn(function)"); } } else { List returnTypes = buildReturnTypes(outputParameterTypes); ParameterizedTypeName parameterizedTupleType = ParameterizedTypeName.get( ClassName.get( "com.cryptape.cita.tuples.generated", "Tuple" + returnTypes.size()), returnTypes.toArray( new TypeName[returnTypes.size()])); methodBuilder.returns(buildRemoteCall(parameterizedTupleType)); buildVariableLengthReturnFunctionConstructor( methodBuilder, functionName, inputParams, outputParameterTypes); buildTupleResultContainer(methodBuilder, parameterizedTupleType, outputParameterTypes); } } private static ParameterizedTypeName buildRemoteCall(TypeName typeName) { return ParameterizedTypeName.get( ClassName.get(RemoteCall.class), typeName); } private static void buildTransactionFunction( AbiDefinition functionDefinition, MethodSpec.Builder methodBuilder, String inputParams) throws ClassNotFoundException { if (functionDefinition.isPayable()) { methodBuilder.addParameter(BigInteger.class, WEI_VALUE); } methodBuilder.addParameter(Long.class, QUOTA) .addParameter(String.class, NONCE) .addParameter(Long.class, VALID_UNTIL_BLOCK) .addParameter(Integer.class, VERSION) .addParameter(BigInteger.class, CHAIN_ID) .addParameter(String.class, VALUE); String functionName = functionDefinition.getName(); methodBuilder.returns(buildRemoteCall(TypeName.get(TransactionReceipt.class))); methodBuilder.addStatement("$T function = new $T(\n$S, \n$T.<$T>asList($L), \n$T" + ".<$T>emptyList())", Function.class, Function.class, functionName, Arrays.class, Type.class, inputParams, Collections.class, TypeReference.class); if (functionDefinition.isPayable()) { methodBuilder.addStatement( "return executeRemoteCallTransaction" + "(function, $N, $N, $N, $N, $N, $N, $N)", WEI_VALUE, QUOTA, NONCE, VALID_UNTIL_BLOCK, VERSION, CHAIN_ID, VALUE); } else { methodBuilder.addStatement("return executeRemoteCallTransaction" + "(function, $N, $N, $N, $N, $N, $N)", QUOTA, NONCE, VALID_UNTIL_BLOCK, VERSION, CHAIN_ID, VALUE); } } TypeSpec buildEventResponseObject(String className, List indexedParameters, List nonIndexedParameters) { TypeSpec.Builder builder = TypeSpec.classBuilder(className) .addModifiers(Modifier.PUBLIC, Modifier.STATIC); for (NamedTypeName namedType : indexedParameters) { TypeName typeName = getIndexedEventWrapperType(namedType.typeName); builder.addField(typeName, namedType.getName(), Modifier.PUBLIC); } for (NamedTypeName namedType : nonIndexedParameters) { TypeName typeName = getWrapperType(namedType.typeName); builder.addField(typeName, namedType.getName(), Modifier.PUBLIC); } return builder.build(); } MethodSpec buildEventFlowableFunction(String responseClassName, String functionName, List indexedParameters, List nonIndexedParameters) throws ClassNotFoundException { String generatedFunctionName = Strings.lowercaseFirstLetter(functionName) + "EventFlowable"; ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(ClassName.get(Flowable.class), ClassName.get("", responseClassName)); MethodSpec.Builder flowableMethodBuilder = MethodSpec.methodBuilder(generatedFunctionName) .addModifiers(Modifier.PUBLIC) .addParameter(DefaultBlockParameter.class, START_BLOCK) .addParameter(DefaultBlockParameter.class, END_BLOCK) .returns(parameterizedTypeName); buildVariableLengthEventConstructor( flowableMethodBuilder, functionName, indexedParameters, nonIndexedParameters); TypeSpec converter = TypeSpec.anonymousClassBuilder("") .addSuperinterface(ParameterizedTypeName.get( ClassName.get(io.reactivex.functions.Function.class), ClassName.get(Log.class), ClassName.get("", responseClassName))) .addMethod(MethodSpec.methodBuilder("apply") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .addParameter(Log.class, "log") .returns(ClassName.get("", responseClassName)) .addStatement("$T eventValues = extractEventParameters(event, log)", EventValues.class) .addStatement("$1T typedResponse = new $1T()", ClassName.get("", responseClassName)) .addCode(buildTypedResponse("typedResponse", indexedParameters, nonIndexedParameters)) .addStatement("return typedResponse") .build()) .build(); flowableMethodBuilder.addStatement("$1T filter = new $1T($2L, $3L, " + "getContractAddress())", AppFilter.class, START_BLOCK, END_BLOCK) .addStatement("filter.addSingleTopic($T.encode(event))", EventEncoder.class) .addStatement("return citaj.appLogFlowable(filter).map($L)", converter); return flowableMethodBuilder .build(); } MethodSpec buildEventTransactionReceiptFunction(String responseClassName, String functionName, List indexedParameters, List nonIndexedParameters) throws ClassNotFoundException { ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get( ClassName.get(List.class), ClassName.get("", responseClassName)); String generatedFunctionName = "get" + Strings.capitaliseFirstLetter(functionName) + "Events"; MethodSpec.Builder transactionMethodBuilder = MethodSpec .methodBuilder(generatedFunctionName) .addModifiers(Modifier.PUBLIC) .addParameter(TransactionReceipt.class, "transactionReceipt") .returns(parameterizedTypeName); buildVariableLengthEventConstructor( transactionMethodBuilder, functionName, indexedParameters, nonIndexedParameters); transactionMethodBuilder.addStatement("$T valueList = extractEventParameters(event, " + "transactionReceipt)", ParameterizedTypeName.get(List.class, EventValues.class)) .addStatement("$1T responses = new $1T(valueList.size())", ParameterizedTypeName.get(ClassName.get(ArrayList.class), ClassName.get("", responseClassName))) .beginControlFlow("for ($T eventValues : valueList)", EventValues.class) .addStatement("$1T typedResponse = new $1T()", ClassName.get("", responseClassName)) .addCode(buildTypedResponse("typedResponse", indexedParameters, nonIndexedParameters)) .addStatement("responses.add(typedResponse)") .endControlFlow(); transactionMethodBuilder.addStatement("return responses"); return transactionMethodBuilder.build(); } void buildEventFunctions( AbiDefinition functionDefinition, TypeSpec.Builder classBuilder) throws ClassNotFoundException { String functionName = functionDefinition.getName(); List inputs = functionDefinition.getInputs(); String responseClassName = Strings.capitaliseFirstLetter(functionName) + "EventResponse"; List indexedParameters = new ArrayList<>(); List nonIndexedParameters = new ArrayList<>(); for (AbiDefinition.NamedType namedType : inputs) { if (namedType.isIndexed()) { indexedParameters.add( new NamedTypeName(namedType.getName(), buildTypeName(namedType.getType()))); } else { nonIndexedParameters.add( new NamedTypeName(namedType.getName(), buildTypeName(namedType.getType()))); } } classBuilder.addType(buildEventResponseObject(responseClassName, indexedParameters, nonIndexedParameters)); classBuilder.addMethod(buildEventTransactionReceiptFunction(responseClassName, functionName, indexedParameters, nonIndexedParameters)); classBuilder.addMethod(buildEventFlowableFunction(responseClassName, functionName, indexedParameters, nonIndexedParameters)); } CodeBlock buildTypedResponse(String objectName, List indexedParameters, List nonIndexedParameters) { String nativeConversion; if (useNativeJavaTypes) { nativeConversion = ".getValue()"; } else { nativeConversion = ""; } CodeBlock.Builder builder = CodeBlock.builder(); for (int i = 0; i < indexedParameters.size(); i++) { builder.addStatement( "$L.$L = ($T) eventValues.getIndexedValues().get($L)" + nativeConversion, objectName, indexedParameters.get(i).getName(), getIndexedEventWrapperType(indexedParameters.get(i).getTypeName()), i); } for (int i = 0; i < nonIndexedParameters.size(); i++) { builder.addStatement( "$L.$L = ($T) eventValues.getNonIndexedValues().get($L)" + nativeConversion, objectName, nonIndexedParameters.get(i).getName(), getWrapperType(nonIndexedParameters.get(i).getTypeName()), i); } return builder.build(); } static TypeName buildTypeName(String typeDeclaration) { String type = trimStorageDeclaration(typeDeclaration); if (type.endsWith("]")) { String[] splitType = type.split("[\\[\\]]"); Class baseType = AbiTypes.getType(splitType[0]); TypeName typeName; if (splitType.length == 1) { typeName = ParameterizedTypeName.get(DynamicArray.class, baseType); } else { Class rawType = getStaticArrayTypeReferenceClass(splitType); typeName = ParameterizedTypeName.get(rawType, baseType); } return typeName; } else { Class cls = AbiTypes.getType(type); return ClassName.get(cls); } } private static Class getStaticArrayTypeReferenceClass(String[] splitType) { try { return Class.forName("com.cryptape.cita.abi.datatypes.generated.StaticArray" + splitType[1]); } catch (ClassNotFoundException e) { // Unfortunately we can't encode it's length as a type if it's > 32. return StaticArray.class; } } private static String trimStorageDeclaration(String type) { if (type.endsWith(" storage") || type.endsWith(" memory")) { return type.split(" ")[0]; } else { return type; } } private List buildReturnTypes(List outputParameterTypes) { List result = new ArrayList<>(outputParameterTypes.size()); for (TypeName typeName : outputParameterTypes) { result.add(getWrapperType(typeName)); } return result; } private static void buildVariableLengthReturnFunctionConstructor( MethodSpec.Builder methodBuilder, String functionName, String inputParameters, List outputParameterTypes) throws ClassNotFoundException { List objects = new ArrayList<>(); objects.add(Function.class); objects.add(Function.class); objects.add(functionName); objects.add(Arrays.class); objects.add(Type.class); objects.add(inputParameters); objects.add(Arrays.class); objects.add(TypeReference.class); for (TypeName outputParameterType : outputParameterTypes) { objects.add(TypeReference.class); objects.add(outputParameterType); } String asListParams = Collection.join( outputParameterTypes, ", ", typeName -> "new $T<$T>() {}"); methodBuilder.addStatement("final $T function = new $T($S, \n$T.<$T>asList($L), \n$T" + ".<$T>asList(" + asListParams + "))", objects.toArray()); } private void buildTupleResultContainer( MethodSpec.Builder methodBuilder, ParameterizedTypeName tupleType, List outputParameterTypes) throws ClassNotFoundException { List typeArguments = tupleType.typeArguments; CodeBlock.Builder tupleConstructor = CodeBlock.builder(); tupleConstructor.addStatement( "$T results = executeCallMultipleValueReturn(function);", ParameterizedTypeName.get(List.class, Type.class)) .add("return new $T(", tupleType) .add("$>$>"); String resultStringSimple = "\n($T) results.get($L)"; if (useNativeJavaTypes) { resultStringSimple += ".getValue()"; } String resultStringNativeList = "\nconvertToNative(($T) results.get($L).getValue())"; int size = typeArguments.size(); ClassName classList = ClassName.get(List.class); for (int i = 0; i < size; i++) { TypeName param = outputParameterTypes.get(i); TypeName convertTo = typeArguments.get(i); String resultString = resultStringSimple; // If we use native java types we need to convert // elements of arrays to native java types too if (useNativeJavaTypes && param instanceof ParameterizedTypeName) { ParameterizedTypeName oldContainer = (ParameterizedTypeName)param; ParameterizedTypeName newContainer = (ParameterizedTypeName)convertTo; if (newContainer.rawType.compareTo(classList) == 0 && newContainer.typeArguments.size() == 1) { convertTo = ParameterizedTypeName.get(classList, oldContainer.typeArguments.get(0)); resultString = resultStringNativeList; } } tupleConstructor .add(resultString, convertTo, i); tupleConstructor.add(i < size - 1 ? ", " : ");\n"); } tupleConstructor.add("$<$<"); TypeSpec callableType = TypeSpec.anonymousClassBuilder("") .addSuperinterface(ParameterizedTypeName.get( ClassName.get(Callable.class), tupleType)) .addMethod(MethodSpec.methodBuilder("call") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .addException(Exception.class) .returns(tupleType) .addCode(tupleConstructor.build()) .build()) .build(); methodBuilder.addStatement( "return new $T(\n$L)", buildRemoteCall(tupleType), callableType); } private static void buildVariableLengthEventConstructor( MethodSpec.Builder methodBuilder, String eventName, List indexedParameterTypes, List nonIndexedParameterTypes) throws ClassNotFoundException { List objects = new ArrayList<>(); objects.add(Event.class); objects.add(Event.class); objects.add(eventName); objects.add(Arrays.class); objects.add(TypeReference.class); for (NamedTypeName indexedParameterType : indexedParameterTypes) { objects.add(TypeReference.class); objects.add(indexedParameterType.getTypeName()); } objects.add(Arrays.class); objects.add(TypeReference.class); for (NamedTypeName indexedParameterType : nonIndexedParameterTypes) { objects.add(TypeReference.class); objects.add(indexedParameterType.getTypeName()); } String indexedAsListParams = Collection.join( indexedParameterTypes, ", ", typeName -> "new $T<$T>() {}"); String nonIndexedAsListParams = Collection.join( nonIndexedParameterTypes, ", ", typeName -> "new $T<$T>() {}"); methodBuilder.addStatement("final $T event = new $T($S, \n" + "$T.<$T>asList(" + indexedAsListParams + "),\n" + "$T.<$T>asList(" + nonIndexedAsListParams + "))", objects.toArray()); } private List loadContractDefinition(String abi) throws IOException { ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper(); AbiDefinition[] abiDefinition = objectMapper.readValue(abi, AbiDefinition[].class); return Arrays.asList(abiDefinition); } private String convertToAbiString(List abi) throws IOException { ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper(); return objectMapper.writeValueAsString(abi); } private static class NamedTypeName { private final TypeName typeName; private final String name; NamedTypeName(String name, TypeName typeName) { this.name = name; this.typeName = typeName; } public String getName() { return name; } public TypeName getTypeName() { return typeName; } } }