org.fisco.bcos.web3j.codegen.SolidityFunctionWrapper Maven / Gradle / Ivy
package org.fisco.bcos.web3j.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.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Modifier;
import org.fisco.bcos.web3j.abi.EventEncoder;
import org.fisco.bcos.web3j.abi.FunctionEncoder;
import org.fisco.bcos.web3j.abi.TypeReference;
import org.fisco.bcos.web3j.abi.datatypes.Address;
import org.fisco.bcos.web3j.abi.datatypes.Bool;
import org.fisco.bcos.web3j.abi.datatypes.DynamicArray;
import org.fisco.bcos.web3j.abi.datatypes.DynamicBytes;
import org.fisco.bcos.web3j.abi.datatypes.Event;
import org.fisco.bcos.web3j.abi.datatypes.Function;
import org.fisco.bcos.web3j.abi.datatypes.StaticArray;
import org.fisco.bcos.web3j.abi.datatypes.Type;
import org.fisco.bcos.web3j.abi.datatypes.Utf8String;
import org.fisco.bcos.web3j.abi.datatypes.generated.AbiTypes;
import org.fisco.bcos.web3j.crypto.Credentials;
import org.fisco.bcos.web3j.protocol.ObjectMapperFactory;
import org.fisco.bcos.web3j.protocol.Web3j;
import org.fisco.bcos.web3j.protocol.core.DefaultBlockParameter;
import org.fisco.bcos.web3j.protocol.core.RemoteCall;
import org.fisco.bcos.web3j.protocol.core.methods.request.BcosFilter;
import org.fisco.bcos.web3j.protocol.core.methods.response.AbiDefinition;
import org.fisco.bcos.web3j.protocol.core.methods.response.Log;
import org.fisco.bcos.web3j.protocol.core.methods.response.TransactionReceipt;
import org.fisco.bcos.web3j.tx.Contract;
import org.fisco.bcos.web3j.tx.TransactionManager;
import org.fisco.bcos.web3j.tx.gas.ContractGasProvider;
import org.fisco.bcos.web3j.utils.Collection;
import org.fisco.bcos.web3j.utils.Strings;
import org.fisco.bcos.web3j.utils.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.squareup.javapoet.AnnotationSpec;
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.sun.media.jfxmedia.logging.Logger;
/** 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 WEB3J = "web3j";
private static final String CREDENTIALS = "credentials";
private static final String CONTRACT_GAS_PROVIDER = "contractGasProvider";
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 FILTER = "filter";
private static final String START_BLOCK = "startBlock";
private static final String END_BLOCK = "endBlock";
private static final String WEI_VALUE = "weiValue";
private static final String FUNC_NAME_PREFIX = "FUNC_";
private static final ClassName LOG = ClassName.get(Log.class);
private static final Logger LOGGER = LoggerFactory.getLogger(SolidityFunctionWrapper.class);
private static final String CODEGEN_WARNING =
"Auto generated code.\n"
+ "
Do not modify!\n"
+ "
Please use the "
+ "web3j command line tools,\n"
+ "or the "
+ SolidityFunctionWrapperGenerator.class.getName()
+ " in the \n"
+ ""
+ "codegen module to update.\n";
private final boolean useNativeJavaTypes;
private static final String regex = "(\\w+)(?:\\[(.*?)\\])(?:\\[(.*?)\\])?";
private static final Pattern pattern = Pattern.compile(regex);
private final GenerationReporter reporter;
public SolidityFunctionWrapper(boolean useNativeJavaTypes) {
this(useNativeJavaTypes, new LogGenerationReporter(LOGGER));
}
SolidityFunctionWrapper(boolean useNativeJavaTypes, GenerationReporter reporter) {
this.useNativeJavaTypes = useNativeJavaTypes;
this.reporter = reporter;
}
@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);
classBuilder.addMethod(buildConstructor(Credentials.class, CREDENTIALS, false));
classBuilder.addMethod(buildConstructor(Credentials.class, CREDENTIALS, true));
classBuilder.addMethod(buildConstructor(TransactionManager.class, TRANSACTION_MANAGER, false));
classBuilder.addMethod(buildConstructor(TransactionManager.class, TRANSACTION_MANAGER, true));
classBuilder.addFields(buildFuncNameConstants(abi));
classBuilder.addMethods(buildFunctionDefinitions(className, classBuilder, abi));
classBuilder.addMethod(buildLoad(className, Credentials.class, CREDENTIALS, false));
classBuilder.addMethod(
buildLoad(className, TransactionManager.class, TRANSACTION_MANAGER, false));
classBuilder.addMethod(buildLoad(className, Credentials.class, CREDENTIALS, true));
classBuilder.addMethod(
buildLoad(className, TransactionManager.class, TRANSACTION_MANAGER, true));
if (!bin.equals(Contract.BIN_NOT_PROVIDED)) {
classBuilder.addMethods(buildDeployMethods(className, classBuilder, abi));
}
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 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 javadoc = CODEGEN_WARNING + getWeb3jVersion();
return TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC)
.addJavadoc(javadoc)
.superclass(Contract.class)
.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "$S", "unchecked").build())
.addField(createBinaryDefinition(binary));
}
private String getWeb3jVersion() {
String version;
try {
// This only works if run as part of the web3j command line tools which contains
// a version.properties file
version = Version.getVersion();
} catch (IOException | NullPointerException e) {
version = Version.DEFAULT;
}
return "\nGenerated with web3j 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 createEventDefinition(String name, List parameters) {
CodeBlock initializer = buildVariableLengthEventInitializer(name, parameters);
return FieldSpec.builder(Event.class, buildEventDefinitionName(name))
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer(initializer)
.build();
}
private String buildEventDefinitionName(String eventName) {
return eventName.toUpperCase() + "_EVENT";
}
private List buildFunctionDefinitions(
String className, TypeSpec.Builder classBuilder, List functionDefinitions)
throws ClassNotFoundException {
List methodSpecs = new ArrayList<>();
for (AbiDefinition functionDefinition : functionDefinitions) {
if (functionDefinition.getType().equals("function")) {
MethodSpec ms = buildFunction(functionDefinition);
methodSpecs.add(ms);
if (!functionDefinition.isConstant()) {
MethodSpec msCallback = buildFunctionWithCallback(functionDefinition);
methodSpecs.add(msCallback);
}
} else if (functionDefinition.getType().equals("event")) {
methodSpecs.addAll(buildEventFunctions(functionDefinition, classBuilder));
}
}
return methodSpecs;
}
List buildDeployMethods(
String className, TypeSpec.Builder classBuilder, List functionDefinitions) {
boolean constructor = false;
List methodSpecs = new ArrayList<>();
for (AbiDefinition functionDefinition : functionDefinitions) {
if (functionDefinition.getType().equals("constructor")) {
constructor = true;
methodSpecs.add(
buildDeploy(className, functionDefinition, Credentials.class, CREDENTIALS, true));
methodSpecs.add(
buildDeploy(
className,
functionDefinition,
TransactionManager.class,
TRANSACTION_MANAGER,
true));
methodSpecs.add(
buildDeploy(className, functionDefinition, Credentials.class, CREDENTIALS, false));
methodSpecs.add(
buildDeploy(
className,
functionDefinition,
TransactionManager.class,
TRANSACTION_MANAGER,
false));
}
}
// constructor will not be specified in ABI file if its empty
if (!constructor) {
MethodSpec.Builder credentialsMethodBuilder =
getDeployMethodSpec(className, Credentials.class, CREDENTIALS, false, true);
methodSpecs.add(
buildDeployNoParams(credentialsMethodBuilder, className, CREDENTIALS, false, true));
MethodSpec.Builder credentialsMethodBuilderNoGasProvider =
getDeployMethodSpec(className, Credentials.class, CREDENTIALS, false, false);
methodSpecs.add(
buildDeployNoParams(
credentialsMethodBuilderNoGasProvider, className, CREDENTIALS, false, false));
MethodSpec.Builder transactionManagerMethodBuilder =
getDeployMethodSpec(
className, TransactionManager.class, TRANSACTION_MANAGER, false, true);
methodSpecs.add(
buildDeployNoParams(
transactionManagerMethodBuilder, className, TRANSACTION_MANAGER, false, true));
MethodSpec.Builder transactionManagerMethodBuilderNoGasProvider =
getDeployMethodSpec(
className, TransactionManager.class, TRANSACTION_MANAGER, false, false);
methodSpecs.add(
buildDeployNoParams(
transactionManagerMethodBuilderNoGasProvider,
className,
TRANSACTION_MANAGER,
false,
false));
}
return methodSpecs;
}
Iterable buildFuncNameConstants(List functionDefinitions) {
List fields = new ArrayList<>();
Set fieldNames = new HashSet<>();
fieldNames.add(Contract.FUNC_DEPLOY);
for (AbiDefinition functionDefinition : functionDefinitions) {
if (functionDefinition.getType().equals("function")) {
String funcName = functionDefinition.getName();
if (!fieldNames.contains(funcName)) {
FieldSpec field =
FieldSpec.builder(
String.class,
funcNameToConst(funcName),
Modifier.PUBLIC,
Modifier.STATIC,
Modifier.FINAL)
.initializer("$S", funcName)
.build();
fields.add(field);
fieldNames.add(funcName);
}
}
}
return fields;
}
private static MethodSpec buildConstructor(
Class authType, String authName, boolean withGasProvider) {
MethodSpec.Builder toReturn =
MethodSpec.constructorBuilder()
.addModifiers(Modifier.PROTECTED)
.addParameter(String.class, CONTRACT_ADDRESS)
.addParameter(Web3j.class, WEB3J)
.addParameter(authType, authName);
if (withGasProvider) {
toReturn
.addParameter(ContractGasProvider.class, CONTRACT_GAS_PROVIDER)
.addStatement(
"super($N, $N, $N, $N, $N)",
BINARY,
CONTRACT_ADDRESS,
WEB3J,
authName,
CONTRACT_GAS_PROVIDER);
} else {
toReturn
.addParameter(BigInteger.class, GAS_PRICE)
.addParameter(BigInteger.class, GAS_LIMIT)
.addStatement(
"super($N, $N, $N, $N, $N, $N)",
BINARY,
CONTRACT_ADDRESS,
WEB3J,
authName,
GAS_PRICE,
GAS_LIMIT)
.addAnnotation(Deprecated.class);
}
return toReturn.build();
}
private MethodSpec buildDeploy(
String className,
AbiDefinition functionDefinition,
Class authType,
String authName,
boolean withGasProvider) {
boolean isPayable = functionDefinition.isPayable();
MethodSpec.Builder methodBuilder =
getDeployMethodSpec(className, authType, authName, isPayable, withGasProvider);
String inputParams = addParameters(methodBuilder, functionDefinition.getInputs());
if (!inputParams.isEmpty()) {
return buildDeployWithParams(
methodBuilder, className, inputParams, authName, isPayable, withGasProvider);
} else {
return buildDeployNoParams(methodBuilder, className, authName, isPayable, withGasProvider);
}
}
private static MethodSpec buildDeployWithParams(
MethodSpec.Builder methodBuilder,
String className,
String inputParams,
String authName,
boolean isPayable,
boolean withGasProvider) {
methodBuilder.addStatement(
"$T encodedConstructor = $T.encodeConstructor(" + "$T.<$T>asList($L)" + ")",
String.class,
FunctionEncoder.class,
Arrays.class,
Type.class,
inputParams);
if (isPayable && !withGasProvider) {
methodBuilder.addStatement(
"return deployRemoteCall(" + "$L.class, $L, $L, $L, $L, $L, encodedConstructor, $L)",
className,
WEB3J,
authName,
GAS_PRICE,
GAS_LIMIT,
BINARY,
INITIAL_VALUE);
methodBuilder.addAnnotation(Deprecated.class);
} else if (isPayable && withGasProvider) {
methodBuilder.addStatement(
"return deployRemoteCall(" + "$L.class, $L, $L, $L, $L, encodedConstructor, $L)",
className,
WEB3J,
authName,
CONTRACT_GAS_PROVIDER,
BINARY,
INITIAL_VALUE);
} else if (!isPayable && !withGasProvider) {
methodBuilder.addStatement(
"return deployRemoteCall($L.class, $L, $L, $L, $L, $L, encodedConstructor)",
className,
WEB3J,
authName,
GAS_PRICE,
GAS_LIMIT,
BINARY);
methodBuilder.addAnnotation(Deprecated.class);
} else {
methodBuilder.addStatement(
"return deployRemoteCall($L.class, $L, $L, $L, $L, encodedConstructor)",
className,
WEB3J,
authName,
CONTRACT_GAS_PROVIDER,
BINARY);
}
return methodBuilder.build();
}
private static MethodSpec buildDeployNoParams(
MethodSpec.Builder methodBuilder,
String className,
String authName,
boolean isPayable,
boolean withGasPRovider) {
if (isPayable && !withGasPRovider) {
methodBuilder.addStatement(
"return deployRemoteCall($L.class, $L, $L, $L, $L, $L, \"\", $L)",
className,
WEB3J,
authName,
GAS_PRICE,
GAS_LIMIT,
BINARY,
INITIAL_VALUE);
methodBuilder.addAnnotation(Deprecated.class);
} else if (isPayable && withGasPRovider) {
methodBuilder.addStatement(
"return deployRemoteCall($L.class, $L, $L, $L, $L, \"\", $L)",
className,
WEB3J,
authName,
CONTRACT_GAS_PROVIDER,
BINARY,
INITIAL_VALUE);
} else if (!isPayable && !withGasPRovider) {
methodBuilder.addStatement(
"return deployRemoteCall($L.class, $L, $L, $L, $L, $L, \"\")",
className,
WEB3J,
authName,
GAS_PRICE,
GAS_LIMIT,
BINARY);
methodBuilder.addAnnotation(Deprecated.class);
} else {
methodBuilder.addStatement(
"return deployRemoteCall($L.class, $L, $L, $L, $L, \"\")",
className,
WEB3J,
authName,
CONTRACT_GAS_PROVIDER,
BINARY);
}
return methodBuilder.build();
}
private static MethodSpec.Builder getDeployMethodSpec(
String className,
Class authType,
String authName,
boolean isPayable,
boolean withGasProvider) {
MethodSpec.Builder builder =
MethodSpec.methodBuilder("deploy")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(buildRemoteCall(TypeVariableName.get(className, Type.class)))
.addParameter(Web3j.class, WEB3J)
.addParameter(authType, authName);
if (isPayable && !withGasProvider) {
return builder
.addParameter(BigInteger.class, GAS_PRICE)
.addParameter(BigInteger.class, GAS_LIMIT)
.addParameter(BigInteger.class, INITIAL_VALUE);
} else if (isPayable && withGasProvider) {
return builder
.addParameter(ContractGasProvider.class, CONTRACT_GAS_PROVIDER)
.addParameter(BigInteger.class, INITIAL_VALUE);
} else if (!isPayable && withGasProvider) {
return builder.addParameter(ContractGasProvider.class, CONTRACT_GAS_PROVIDER);
} else {
return builder
.addParameter(BigInteger.class, GAS_PRICE)
.addParameter(BigInteger.class, GAS_LIMIT);
}
}
private static MethodSpec buildLoad(
String className, Class authType, String authName, boolean withGasProvider) {
MethodSpec.Builder toReturn =
MethodSpec.methodBuilder("load")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(TypeVariableName.get(className, Type.class))
.addParameter(String.class, CONTRACT_ADDRESS)
.addParameter(Web3j.class, WEB3J)
.addParameter(authType, authName);
if (withGasProvider) {
toReturn
.addParameter(ContractGasProvider.class, CONTRACT_GAS_PROVIDER)
.addStatement(
"return new $L($L, $L, $L, $L)",
className,
CONTRACT_ADDRESS,
WEB3J,
authName,
CONTRACT_GAS_PROVIDER);
} else {
toReturn
.addParameter(BigInteger.class, GAS_PRICE)
.addParameter(BigInteger.class, GAS_LIMIT)
.addStatement(
"return new $L($L, $L, $L, $L, $L)",
className,
CONTRACT_ADDRESS,
WEB3J,
authName,
GAS_PRICE,
GAS_LIMIT)
.addAnnotation(Deprecated.class);
}
return toReturn.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
this::createMappedParameterTypes);
} 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 {
String parameterSpecType = parameterSpec.type.toString();
TypeName typeName = typeNames.get(0);
String typeMapInput = typeName + ".class";
if (typeName instanceof ParameterizedTypeName) {
List typeArguments = ((ParameterizedTypeName) typeName).typeArguments;
if (typeArguments.size() != 1) {
throw new UnsupportedOperationException(
"Only a single parameterized type is supported");
}
TypeName innerTypeName = typeArguments.get(0);
parameterSpecType = ((ParameterizedTypeName) parameterSpec.type).rawType.toString();
typeMapInput =
((ParameterizedTypeName) typeName).rawType + ".class, " + innerTypeName + ".class";
}
return "new "
+ parameterSpecType
+ "(\n"
+ " org.fisco.bcos.web3j.abi.Utils.typeMap("
+ parameterSpec.name
+ ", "
+ typeMapInput
+ "))";
}
} 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 ("Utf8String".equals(simpleName)) {
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();
if(!SourceVersion.isName(functionName)) {
functionName = "_" + functionName;
}
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();
}
MethodSpec buildFunctionWithCallback(AbiDefinition functionDefinition)
throws ClassNotFoundException {
String functionName = functionDefinition.getName();
MethodSpec.Builder methodBuilder =
MethodSpec.methodBuilder(functionName).addModifiers(Modifier.PUBLIC);
List outputParameterTypes = buildTypeNames(functionDefinition.getOutputs());
if (functionDefinition.isConstant()) {
String inputParams = addParameters(methodBuilder, functionDefinition.getInputs());
buildConstantFunction(functionDefinition, methodBuilder, outputParameterTypes, inputParams);
} else {
// functionDefinition.getInputs().add(new NamedType("callback",
// "org.fisco.bcos.channel.dto.FiscoResponse.TransactionSucCallback"));
String inputParams = addParameters(methodBuilder, functionDefinition.getInputs());
methodBuilder.addParameter(
ParameterSpec.builder(buildTypeName("TransactionSucCallback"), "callback").build());
buildTransactionFunctionWithCallback(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()) {
methodBuilder.addStatement(
"throw new RuntimeException"
+ "(\"cannot call constant function with void return type\")");
} 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(
"final $T function = "
+ "new $T($N, \n$T.<$T>asList($L), "
+ "\n$T.<$T>>asList(new $T<$T>() {}))",
Function.class,
Function.class,
funcNameToConst(functionName),
Arrays.class,
Type.class,
inputParams,
Arrays.class,
TypeReference.class,
TypeReference.class,
typeName);
if (useNativeJavaTypes) {
if (nativeReturnTypeName.equals(ClassName.get(List.class))) {
// We return list. So all the list elements should
// also be converted to native types
TypeName listType = ParameterizedTypeName.get(List.class, Type.class);
CodeBlock.Builder callCode = CodeBlock.builder();
callCode.addStatement(
"$T result = " + "($T) executeCallSingleValueReturn(function, $T.class)",
listType,
listType,
nativeReturnTypeName);
callCode.addStatement("return convertToNative(result)");
TypeSpec callableType =
TypeSpec.anonymousClassBuilder("")
.addSuperinterface(
ParameterizedTypeName.get(
ClassName.get(Callable.class), nativeReturnTypeName))
.addMethod(
MethodSpec.methodBuilder("call")
.addAnnotation(Override.class)
.addAnnotation(
AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "$S", "unchecked")
.build())
.addModifiers(Modifier.PUBLIC)
.addException(Exception.class)
.returns(nativeReturnTypeName)
.addCode(callCode.build())
.build())
.build();
methodBuilder.addStatement(
"return new $T(\n$L)", buildRemoteCall(nativeReturnTypeName), callableType);
} else {
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("org.fisco.bcos.web3j.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 void buildTransactionFunction(
AbiDefinition functionDefinition, MethodSpec.Builder methodBuilder, String inputParams)
throws ClassNotFoundException {
if (functionDefinition.hasOutputs()) {
// CHECKSTYLE:OFF
reporter.report(
String.format(
"Definition of the function %s returns a value but is not defined as a view function. "
+ "Please ensure it contains the view modifier if you want to read the return value",
functionDefinition.getName()));
// CHECKSTYLE:ON
}
if (functionDefinition.isPayable()) {
methodBuilder.addParameter(BigInteger.class, WEI_VALUE);
}
String functionName = functionDefinition.getName();
methodBuilder.returns(buildRemoteCall(TypeName.get(TransactionReceipt.class)));
methodBuilder.addStatement(
"final $T function = new $T(\n$N, \n$T.<$T>asList($L), \n$T" + ".<$T>>emptyList())",
Function.class,
Function.class,
funcNameToConst(functionName),
Arrays.class,
Type.class,
inputParams,
Collections.class,
TypeReference.class);
if (functionDefinition.isPayable()) {
methodBuilder.addStatement("return executeRemoteCallTransaction(function, $N)", WEI_VALUE);
} else {
methodBuilder.addStatement("return executeRemoteCallTransaction(function)");
}
}
private void buildTransactionFunctionWithCallback(
AbiDefinition functionDefinition, MethodSpec.Builder methodBuilder, String inputParams)
throws ClassNotFoundException {
if (functionDefinition.hasOutputs()) {
// CHECKSTYLE:OFF
reporter.report(
String.format(
"Definition of the function %s returns a value but is not defined as a view function. "
+ "Please ensure it contains the view modifier if you want to read the return value",
functionDefinition.getName()));
// CHECKSTYLE:ON
}
if (functionDefinition.isPayable()) {
methodBuilder.addParameter(BigInteger.class, WEI_VALUE);
}
// methodBuilder.addParameter(TransactionSucCallback.class, "callback");
String functionName = functionDefinition.getName();
methodBuilder.returns(TypeName.VOID);
methodBuilder.addStatement(
"final $T function = new $T(\n$N, \n$T.<$T>asList($L), \n$T" + ".<$T>>emptyList())",
Function.class,
Function.class,
funcNameToConst(functionName),
Arrays.class,
Type.class,
inputParams,
Collections.class,
TypeReference.class);
methodBuilder.addStatement("asyncExecuteTransaction(function, callback)");
}
TypeSpec buildEventResponseObject(
String className,
List indexedParameters,
List nonIndexedParameters) {
TypeSpec.Builder builder =
TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC, Modifier.STATIC);
builder.addField(LOG, "log", Modifier.PUBLIC);
for (SolidityFunctionWrapper.NamedTypeName namedType : indexedParameters) {
TypeName typeName = getIndexedEventWrapperType(namedType.typeName);
builder.addField(typeName, namedType.getName(), Modifier.PUBLIC);
}
for (SolidityFunctionWrapper.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(BcosFilter.class, FILTER)
.returns(parameterizedTypeName);
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 = extractEventParametersWithLog("
+ buildEventDefinitionName(functionName)
+ ", log)",
Contract.EventValuesWithLog.class)
.addStatement(
"$1T typedResponse = new $1T()", ClassName.get("", responseClassName))
.addCode(
buildTypedResponse(
"typedResponse", indexedParameters, nonIndexedParameters, true))
.addStatement("return typedResponse")
.build())
.build();
flowableMethodBuilder.addStatement("return web3j.logFlowable(filter).map($L)", converter);
return flowableMethodBuilder.build();
}
MethodSpec buildDefaultEventFlowableFunction(String responseClassName, String functionName) {
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);
flowableMethodBuilder
.addStatement(
"$1T filter = new $1T($2L, $3L, " + "getContractAddress())",
BcosFilter.class,
START_BLOCK,
END_BLOCK)
.addStatement(
"filter.addSingleTopic($T.encode(" + buildEventDefinitionName(functionName) + "))",
EventEncoder.class)
.addStatement("return " + generatedFunctionName + "(filter)");
return flowableMethodBuilder.build();
}
MethodSpec buildEventTransactionReceiptFunction(
String responseClassName,
String functionName,
List indexedParameters,
List nonIndexedParameters) {
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);
transactionMethodBuilder
.addStatement(
"$T valueList = extractEventParametersWithLog("
+ buildEventDefinitionName(functionName)
+ ", "
+ "transactionReceipt)",
ParameterizedTypeName.get(List.class, Contract.EventValuesWithLog.class))
.addStatement(
"$1T responses = new $1T(valueList.size())",
ParameterizedTypeName.get(
ClassName.get(ArrayList.class), ClassName.get("", responseClassName)))
.beginControlFlow("for ($T eventValues : valueList)", Contract.EventValuesWithLog.class)
.addStatement("$1T typedResponse = new $1T()", ClassName.get("", responseClassName))
.addCode(
buildTypedResponse("typedResponse", indexedParameters, nonIndexedParameters, false))
.addStatement("responses.add(typedResponse)")
.endControlFlow();
transactionMethodBuilder.addStatement("return responses");
return transactionMethodBuilder.build();
}
List buildEventFunctions(
AbiDefinition functionDefinition, TypeSpec.Builder classBuilder)
throws ClassNotFoundException {
String functionName = functionDefinition.getName();
List inputs = functionDefinition.getInputs();
String responseClassName = Strings.capitaliseFirstLetter(functionName) + "EventResponse";
List parameters = new ArrayList<>();
List indexedParameters = new ArrayList<>();
List nonIndexedParameters = new ArrayList<>();
for (AbiDefinition.NamedType namedType : inputs) {
NamedTypeName parameter =
new NamedTypeName(
namedType.getName(), buildTypeName(namedType.getType()), namedType.isIndexed());
if (namedType.isIndexed()) {
indexedParameters.add(parameter);
} else {
nonIndexedParameters.add(parameter);
}
parameters.add(parameter);
}
classBuilder.addField(createEventDefinition(functionName, parameters));
classBuilder.addType(
buildEventResponseObject(responseClassName, indexedParameters, nonIndexedParameters));
List methods = new ArrayList<>();
methods.add(
buildEventTransactionReceiptFunction(
responseClassName, functionName, indexedParameters, nonIndexedParameters));
methods.add(
buildEventFlowableFunction(
responseClassName, functionName, indexedParameters, nonIndexedParameters));
methods.add(buildDefaultEventFlowableFunction(responseClassName, functionName));
return methods;
}
CodeBlock buildTypedResponse(
String objectName,
List indexedParameters,
List nonIndexedParameters,
boolean flowable) {
String nativeConversion;
if (useNativeJavaTypes) {
nativeConversion = ".getValue()";
} else {
nativeConversion = "";
}
CodeBlock.Builder builder = CodeBlock.builder();
if (flowable) {
builder.addStatement("$L.log = log", objectName);
} else {
builder.addStatement("$L.log = eventValues.getLog()", objectName);
}
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);
Matcher matcher = pattern.matcher(type);
if (matcher.find()) {
Class> baseType = AbiTypes.getType(matcher.group(1));
String firstArrayDimension = matcher.group(2);
String secondArrayDimension = matcher.group(3);
TypeName typeName;
if ("".equals(firstArrayDimension)) {
typeName = ParameterizedTypeName.get(DynamicArray.class, baseType);
} else {
Class> rawType = getStaticArrayTypeReferenceClass(firstArrayDimension);
typeName = ParameterizedTypeName.get(rawType, baseType);
}
if (secondArrayDimension != null) {
if ("".equals(secondArrayDimension)) {
return ParameterizedTypeName.get(ClassName.get(DynamicArray.class), typeName);
} else {
Class> rawType = getStaticArrayTypeReferenceClass(secondArrayDimension);
return ParameterizedTypeName.get(ClassName.get(rawType), typeName);
}
}
return typeName;
} else {
Class> cls = AbiTypes.getType(type);
return ClassName.get(cls);
}
}
private static Class> getStaticArrayTypeReferenceClass(String type) {
try {
return Class.forName("org.fisco.bcos.web3j.abi.datatypes.generated.StaticArray" + type);
} 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