
com.cryptape.cita.codegen.SolidityFunctionWrapper Maven / Gradle / Ivy
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 "\nGenerated 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