org.fisco.bcos.sdk.codegen.SolidityContractWrapper Maven / Gradle / Ivy
/**
* Copyright 2014-2020 [fisco-dev]
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
*
http://www.apache.org/licenses/LICENSE-2.0
*
*
Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fisco.bcos.sdk.codegen;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
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 java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.sdk.abi.FunctionEncoder;
import org.fisco.bcos.sdk.abi.FunctionReturnDecoder;
import org.fisco.bcos.sdk.abi.TypeReference;
import org.fisco.bcos.sdk.abi.datatypes.Address;
import org.fisco.bcos.sdk.abi.datatypes.Bool;
import org.fisco.bcos.sdk.abi.datatypes.DynamicArray;
import org.fisco.bcos.sdk.abi.datatypes.DynamicBytes;
import org.fisco.bcos.sdk.abi.datatypes.Event;
import org.fisco.bcos.sdk.abi.datatypes.Function;
import org.fisco.bcos.sdk.abi.datatypes.StaticArray;
import org.fisco.bcos.sdk.abi.datatypes.Type;
import org.fisco.bcos.sdk.abi.datatypes.Utf8String;
import org.fisco.bcos.sdk.abi.datatypes.generated.AbiTypes;
import org.fisco.bcos.sdk.abi.wrapper.ABIDefinition;
import org.fisco.bcos.sdk.client.Client;
import org.fisco.bcos.sdk.codegen.exceptions.CodeGenException;
import org.fisco.bcos.sdk.contract.Contract;
import org.fisco.bcos.sdk.crypto.CryptoSuite;
import org.fisco.bcos.sdk.crypto.keypair.CryptoKeyPair;
import org.fisco.bcos.sdk.eventsub.EventCallback;
import org.fisco.bcos.sdk.model.CryptoType;
import org.fisco.bcos.sdk.model.TransactionReceipt;
import org.fisco.bcos.sdk.model.callback.TransactionCallback;
import org.fisco.bcos.sdk.transaction.model.exception.ContractException;
import org.fisco.bcos.sdk.utils.Collection;
import org.fisco.bcos.sdk.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Generate Java Classes based on generated Solidity bin and abi files. */
public class SolidityContractWrapper {
private static final Logger logger = LoggerFactory.getLogger(SolidityContractWrapper.class);
private static final int maxSolidityBinSize = 0x40000;
private static final int maxField = 8 * 1024;
private static String BINARY_ARRAY_NAME = "BINARY_ARRAY";
private static String SM_BINARY_ARRAY_NAME = "SM_BINARY_ARRAY";
private static final String BINARY_NAME = "BINARY";
private static final String SM_BINARY_NAME = "SM_BINARY";
private static final String ABI_ARRAY_NAME = "ABI_ARRAY";
private static final String ABI_NAME = "ABI";
private static final String GET_BINARY_FUNC = "getBinary";
private static final String CLIENT = "client";
private static final String CREDENTIAL = "credential";
private static final String CRYPTOSUITE = "cryptoSuite";
private static final String CONTRACT_ADDRESS = "contractAddress";
private static final String FROM_BLOCK = "fromBlock";
private static final String TO_BLOCK = "toBlock";
private static final String CALLBACK_VALUE = "callback";
private static final String OTHER_TOPICS = "otherTopics";
private static final String FUNC_NAME_PREFIX = "FUNC_";
private static final String EVENT_ENCODER = "eventEncoder";
private static final String regex = "(\\w+)(?:\\[(.*?)\\])(?:\\[(.*?)\\])?";
private static final Pattern pattern = Pattern.compile(regex);
public void generateJavaFiles(
String contractName,
String bin,
String smBin,
String abi,
String destinationDir,
String basePackageName)
throws IOException, ClassNotFoundException, UnsupportedOperationException,
CodeGenException {
String className = StringUtils.capitaliseFirstLetter(contractName);
logger.info("bin: {}", bin);
logger.info("smBin: {}", smBin);
if (bin.length() > maxSolidityBinSize) {
throw new UnsupportedOperationException(
" contract binary too long, max support is 256k, now is "
+ Integer.valueOf(bin.length()));
}
List abiDefinitions = CodeGenUtils.loadContractAbiDefinition(abi);
TypeSpec.Builder classBuilder = createClassBuilder(className, bin, smBin, abi);
classBuilder.addMethod(
buildGetBinaryMethod(CryptoSuite.class, CryptoType.class, CRYPTOSUITE));
classBuilder.addMethod(buildConstructor(CryptoKeyPair.class, CREDENTIAL));
classBuilder.addFields(buildFuncNameConstants(abiDefinitions));
classBuilder.addMethods(buildFunctionDefinitions(classBuilder, abiDefinitions));
classBuilder.addMethod(buildLoad(className, CryptoKeyPair.class, CREDENTIAL));
classBuilder.addMethods(buildDeployMethods(className, abiDefinitions));
write(basePackageName, classBuilder.build(), destinationDir);
}
protected void write(String packageName, TypeSpec typeSpec, String destinationDir)
throws IOException {
JavaFile javaFile =
JavaFile.builder(packageName, typeSpec)
.indent(" ")
.skipJavaLangImports(true)
.build();
javaFile.writeTo(new File(destinationDir));
}
private TypeSpec.Builder createClassBuilder(
String className, String binary, String smBinary, String abi) {
TypeSpec.Builder builder =
TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC)
.superclass(Contract.class)
.addAnnotation(
AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "$S", "unchecked")
.build())
// binary fields
.addField(createArrayDefinition(BINARY_ARRAY_NAME, binary))
.addField(createDefinition(BINARY_NAME, BINARY_ARRAY_NAME))
.addField(createArrayDefinition(SM_BINARY_ARRAY_NAME, smBinary))
.addField(createDefinition(SM_BINARY_NAME, SM_BINARY_ARRAY_NAME))
// abi fields
.addField(createArrayDefinition(ABI_ARRAY_NAME, abi))
.addField(createDefinition(ABI_NAME, ABI_ARRAY_NAME));
return builder;
}
public List stringToArrayString(String binary) {
List binaryArray = new ArrayList();
for (int offset = 0; offset < binary.length(); ) {
int length = binary.length() - offset;
if (length > maxField) {
length = maxField;
}
String item = binary.substring(offset, offset + length);
binaryArray.add(item);
offset += item.length();
}
return binaryArray;
}
private FieldSpec createArrayDefinition(String type, String binary) {
List binaryArray = stringToArrayString(binary);
List formatArray =
new ArrayList(Collections.nCopies(binaryArray.size(), "$S"));
return FieldSpec.builder(String[].class, type)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
.initializer("{" + String.join(",", formatArray) + "}", binaryArray.toArray())
.build();
}
private FieldSpec createDefinition(String type, String binayArrayName) {
return FieldSpec.builder(String.class, type)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
.initializer("String.join(\"\", " + binayArrayName + ")")
.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 static boolean isOverLoadFunction(
String name, List functionDefinitions) {
int count = 0;
for (ABIDefinition functionDefinition : functionDefinitions) {
if (!functionDefinition.getType().equals("function")) {
continue;
}
if (functionDefinition.getName().equals(name)) {
count += 1;
}
}
return count > 1;
}
private List buildFunctionDefinitions(
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);
MethodSpec msSeq = buildFunctionSignedTransaction(functionDefinition);
methodSpecs.add(msSeq);
boolean isOverLoad =
isOverLoadFunction(functionDefinition.getName(), functionDefinitions);
if (!functionDefinition.getInputs().isEmpty()) {
MethodSpec inputDecoder =
buildFunctionWithInputDecoder(functionDefinition, isOverLoad);
methodSpecs.add(inputDecoder);
}
if (!functionDefinition.getOutputs().isEmpty()) {
MethodSpec outputDecoder =
buildFunctionWithOutputDecoder(functionDefinition, isOverLoad);
methodSpecs.add(outputDecoder);
}
}
} else if (functionDefinition.getType().equals("event")) {
methodSpecs.addAll(buildEventFunctions(functionDefinition, classBuilder));
}
}
return methodSpecs;
}
private List buildDeployMethods(
String className, 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, CryptoKeyPair.class, CREDENTIAL));
}
}
// constructor will not be specified in ABI file if its empty
if (!constructor) {
MethodSpec.Builder credentialsMethodBuilder =
getDeployMethodSpec(className, CryptoKeyPair.class, CREDENTIAL);
methodSpecs.add(buildDeployNoParams(credentialsMethodBuilder, className, CREDENTIAL));
}
return methodSpecs;
}
private 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 buildGetBinaryMethod(
Class authType, Class cryptoType, String authName) {
MethodSpec.Builder toReturn =
MethodSpec.methodBuilder(GET_BINARY_FUNC)
.addParameter(authType, authName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(String.class);
toReturn.addStatement(
"return ($N.getCryptoTypeConfig() == $T.ECDSA_TYPE ? $N : $N)",
authName,
cryptoType,
BINARY_NAME,
SM_BINARY_NAME);
return toReturn.build();
}
private static MethodSpec buildConstructor(Class authType, String authName) {
MethodSpec.Builder toReturn =
MethodSpec.constructorBuilder()
.addModifiers(Modifier.PROTECTED)
.addParameter(String.class, CONTRACT_ADDRESS)
.addParameter(Client.class, CLIENT)
.addParameter(authType, authName)
.addStatement(
"super($N, $N, $N, $N)",
getBinaryFuncDefinition(),
CONTRACT_ADDRESS,
CLIENT,
authName);
return toReturn.build();
}
private MethodSpec buildDeploy(
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) {
methodBuilder
.addStatement(
"$T encodedConstructor = $T.encodeConstructor(" + "$T.<$T>asList($L)" + ")",
String.class,
FunctionEncoder.class,
Arrays.class,
Type.class,
inputParams)
.addStatement(
"return deploy(" + "$L.class, $L, $L, $L, encodedConstructor)",
className,
CLIENT,
authName,
getBinaryFuncDefinition());
return methodBuilder.build();
}
private static MethodSpec buildDeployNoParams(
MethodSpec.Builder methodBuilder, String className, String authName) {
methodBuilder.addStatement(
"return deploy($L.class, $L, $L, $L, \"\")",
className,
CLIENT,
authName,
getBinaryFuncDefinition());
return methodBuilder.build();
}
private static MethodSpec.Builder getDeployMethodSpec(
String className, Class authType, String authName) {
return MethodSpec.methodBuilder("deploy")
.addException(ContractException.class)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(TypeVariableName.get(className, Type.class))
.addParameter(Client.class, CLIENT)
.addParameter(authType, authName);
}
private static MethodSpec buildLoad(String className, Class authType, String authName) {
MethodSpec.Builder toReturn =
MethodSpec.methodBuilder("load")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(TypeVariableName.get(className, Type.class))
.addParameter(String.class, CONTRACT_ADDRESS)
.addParameter(Client.class, CLIENT)
.addParameter(authType, authName)
.addStatement(
"return new $L($L, $L, $L)",
className,
CONTRACT_ADDRESS,
CLIENT,
authName);
return toReturn.build();
}
private MethodSpec.Builder addParameter(
MethodSpec.Builder methodBuilder, String type, String name) {
ParameterSpec parameterSpec = buildParameterType(type, name);
TypeName typeName = getNativeType(parameterSpec.type);
ParameterSpec inputParameter = ParameterSpec.builder(typeName, parameterSpec.name).build();
methodBuilder.addParameter(inputParameter);
return methodBuilder;
}
private String addParameters(
MethodSpec.Builder methodBuilder, List namedTypes) {
List inputParameterTypes = buildParameterTypes(namedTypes);
List nativeInputParameterTypes = new ArrayList<>(inputParameterTypes.size());
for (ParameterSpec parameterSpec : inputParameterTypes) {
TypeName typeName = getNativeType(parameterSpec.type);
nativeInputParameterTypes.add(
ParameterSpec.builder(typeName, parameterSpec.name).build());
}
methodBuilder.addParameters(nativeInputParameterTypes);
return Collection.join(
namedTypes,
", \n",
// this results in fully qualified names being generated
this::createMappedParameterTypes);
}
private String addParameters(List namedTypes) {
List inputParameterTypes = buildParameterTypes(namedTypes);
List nativeInputParameterTypes = new ArrayList<>(inputParameterTypes.size());
for (ParameterSpec parameterSpec : inputParameterTypes) {
TypeName typeName = getNativeType(parameterSpec.type);
nativeInputParameterTypes.add(
ParameterSpec.builder(typeName, parameterSpec.name).build());
}
return Collection.join(
namedTypes,
", \n",
// this results in fully qualified names being generated
this::createMappedParameterTypes);
}
private String createMappedParameterTypes(ABIDefinition.NamedType namedType) {
String name = namedType.getName();
String type = namedType.getType();
ABIDefinition.Type innerType = new ABIDefinition.Type(type);
ParameterSpec parameterSpec = ParameterSpec.builder(buildTypeName(type), name).build();
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";
}
if (innerType.isDynamicList()) { // dynamic array
return parameterSpec.name
+ ".isEmpty()?org.fisco.bcos.sdk.abi.datatypes.DynamicArray.empty"
+ "(\""
+ type
+ "\"):"
+ "new "
+ parameterSpecType
+ "(\n"
+ " org.fisco.bcos.sdk.abi.Utils.typeMap("
+ parameterSpec.name
+ ", "
+ typeMapInput
+ "))";
} else { // static array
return "new "
+ parameterSpecType
+ "(\n"
+ " org.fisco.bcos.sdk.abi.Utils.typeMap("
+ parameterSpec.name
+ ", "
+ typeMapInput
+ "))";
}
}
} else {
return "new " + parameterSpec.type + "(" + parameterSpec.name + ")";
}
}
private TypeName getWrapperRawType(TypeName typeName) {
if (typeName instanceof ParameterizedTypeName) {
return ClassName.get(List.class);
}
return getNativeType(typeName);
}
protected static TypeName getNativeType(TypeName typeName) {
if (typeName instanceof ParameterizedTypeName) {
return getNativeType((ParameterizedTypeName) typeName);
}
String simpleName = ((ClassName) typeName).simpleName();
if (simpleName.startsWith(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.startsWith(Utf8String.class.getSimpleName())) {
return TypeName.get(String.class);
} else if (simpleName.startsWith("Bytes")) {
return TypeName.get(byte[].class);
} else if (simpleName.startsWith(DynamicBytes.class.getSimpleName())) {
return TypeName.get(byte[].class);
} else if (simpleName.startsWith(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.");
}
}
protected 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()]));
}
protected 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);
}
}
private ParameterSpec buildParameterType(String type, String name) {
return ParameterSpec.builder(buildTypeName(type), name).build();
}
protected 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();
namedType.setName(name);
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
*/
protected static String createValidParamName(String name, int idx) {
if (name.equals("")) {
return "param" + idx;
} else {
return name;
}
}
protected static List buildTypeNames(List namedTypes) {
List result = new ArrayList<>(namedTypes.size());
for (ABIDefinition.NamedType namedType : namedTypes) {
result.add(buildTypeName(namedType.getType()));
}
return result;
}
private 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();
}
private MethodSpec buildFunctionSignedTransaction(ABIDefinition functionDefinition)
throws ClassNotFoundException {
String functionName = "getSignedTransactionFor";
functionName += StringUtils.capitaliseFirstLetter(functionDefinition.getName());
if (!SourceVersion.isName(functionName)) {
functionName = "_" + functionName;
}
MethodSpec.Builder methodBuilder =
MethodSpec.methodBuilder(functionName).addModifiers(Modifier.PUBLIC);
String inputParams = addParameters(methodBuilder, functionDefinition.getInputs());
buildTransactionFunctionSeq(functionDefinition, methodBuilder, inputParams);
return methodBuilder.build();
}
private 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 {
String inputParams = addParameters(methodBuilder, functionDefinition.getInputs());
methodBuilder.addParameter(TransactionCallback.class, "callback");
buildTransactionFunctionWithCallback(functionDefinition, methodBuilder, inputParams);
}
return methodBuilder.build();
}
public static String getInputOutputFunctionName(
ABIDefinition functionDefinition, boolean isOverLoad) {
if (!isOverLoad) {
return functionDefinition.getName();
}
List nameTypes = functionDefinition.getInputs();
String name = functionDefinition.getName();
for (int i = 0; i < nameTypes.size(); i++) {
ABIDefinition.Type type = nameTypes.get(i).newType();
name += StringUtils.capitaliseFirstLetter(type.getRawType());
if (!type.isList()) {
continue;
}
// parse the array or the struct
List depths = type.getDimensions();
for (int j = 0; j < depths.size(); j++) {
name += type.getRawType();
if (0 != depths.get(j)) {
name += String.valueOf(depths.get(j));
}
}
}
logger.debug(" name: {}, nameTypes: {}", name, nameTypes);
return name;
}
private MethodSpec buildFunctionWithInputDecoder(
ABIDefinition functionDefinition, boolean isOverLoad) throws ClassNotFoundException {
String functionName = getInputOutputFunctionName(functionDefinition, isOverLoad);
MethodSpec.Builder methodBuilder =
MethodSpec.methodBuilder(
"get" + StringUtils.capitaliseFirstLetter(functionName) + "Input")
.addModifiers(Modifier.PUBLIC)
.addParameter(TransactionReceipt.class, "transactionReceipt");
List returnTypes =
buildReturnTypes(buildTypeNames(functionDefinition.getInputs()));
ParameterizedTypeName parameterizedTupleType =
ParameterizedTypeName.get(
ClassName.get(
"org.fisco.bcos.sdk.abi.datatypes.generated.tuples.generated",
"Tuple" + returnTypes.size()),
returnTypes.toArray(new TypeName[returnTypes.size()]));
methodBuilder.returns(parameterizedTupleType);
methodBuilder.addStatement("String data = transactionReceipt.getInput().substring(10)");
buildVariableLengthReturnFunctionConstructor(
methodBuilder,
functionDefinition.getName(),
"",
buildTypeNames(functionDefinition.getInputs()));
methodBuilder.addStatement(
"$T results = $T.decode(data, function.getOutputParameters())",
List.class,
FunctionReturnDecoder.class);
buildTupleResultContainer0(
methodBuilder,
parameterizedTupleType,
buildTypeNames(functionDefinition.getInputs()));
return methodBuilder.build();
}
private MethodSpec buildFunctionWithOutputDecoder(
ABIDefinition functionDefinition, boolean isOverLoad) throws ClassNotFoundException {
String functionName = getInputOutputFunctionName(functionDefinition, isOverLoad);
MethodSpec.Builder methodBuilder =
MethodSpec.methodBuilder(
"get" + StringUtils.capitaliseFirstLetter(functionName) + "Output")
.addModifiers(Modifier.PUBLIC)
.addParameter(TransactionReceipt.class, "transactionReceipt");
List returnTypes =
buildReturnTypes(buildTypeNames(functionDefinition.getOutputs()));
ParameterizedTypeName parameterizedTupleType =
ParameterizedTypeName.get(
ClassName.get(
"org.fisco.bcos.sdk.abi.datatypes.generated.tuples.generated",
"Tuple" + returnTypes.size()),
returnTypes.toArray(new TypeName[returnTypes.size()]));
methodBuilder.returns(parameterizedTupleType);
methodBuilder.addStatement("String data = transactionReceipt.getOutput()");
buildVariableLengthReturnFunctionConstructor(
methodBuilder,
functionDefinition.getName(),
"",
buildTypeNames(functionDefinition.getOutputs()));
methodBuilder.addStatement(
"$T results = $T.decode(data, function.getOutputParameters())",
List.class,
FunctionReturnDecoder.class);
buildTupleResultContainer0(
methodBuilder,
parameterizedTupleType,
buildTypeNames(functionDefinition.getOutputs()));
return methodBuilder.build();
}
private void buildConstantFunction(
ABIDefinition functionDefinition,
MethodSpec.Builder methodBuilder,
List outputParameterTypes,
String inputParams)
throws ClassNotFoundException {
String functionName = functionDefinition.getName();
methodBuilder.addException(ContractException.class);
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;
nativeReturnTypeName = getWrapperRawType(typeName);
methodBuilder.returns(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 (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) executeCallWithSingleValueReturn(function, $T.class)",
listType,
listType,
nativeReturnTypeName);
callCode.addStatement("return convertToNative(result)");
methodBuilder.returns(nativeReturnTypeName).addCode(callCode.build());
} else {
methodBuilder.addStatement(
"return executeCallWithSingleValueReturn(function, $T.class)",
nativeReturnTypeName);
}
} else {
List returnTypes = buildReturnTypes(outputParameterTypes);
ParameterizedTypeName parameterizedTupleType =
ParameterizedTypeName.get(
ClassName.get(
"org.fisco.bcos.sdk.abi.datatypes.generated.tuples.generated",
"Tuple" + returnTypes.size()),
returnTypes.toArray(new TypeName[returnTypes.size()]));
methodBuilder.returns(parameterizedTupleType);
buildVariableLengthReturnFunctionConstructor(
methodBuilder, functionName, inputParams, outputParameterTypes);
buildTupleResultContainer(methodBuilder, parameterizedTupleType, outputParameterTypes);
}
}
private void buildTransactionFunction(
ABIDefinition functionDefinition, MethodSpec.Builder methodBuilder, String inputParams)
throws ClassNotFoundException {
String functionName = functionDefinition.getName();
methodBuilder.returns(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);
methodBuilder.addStatement("return executeTransaction(function)");
}
private void buildTransactionFunctionWithCallback(
ABIDefinition functionDefinition, MethodSpec.Builder methodBuilder, String inputParams)
throws ClassNotFoundException {
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)");
}
private void buildTransactionFunctionSeq(
ABIDefinition functionDefinition, MethodSpec.Builder methodBuilder, String inputParams)
throws ClassNotFoundException {
String functionName = functionDefinition.getName();
TypeName returnType = TypeName.get(String.class);
methodBuilder.returns(returnType);
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("return createSignedTransaction(function)");
}
private TypeSpec buildEventResponseObject(
String className,
List indexedParameters,
List nonIndexedParameters) {
TypeSpec.Builder builder =
TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC, Modifier.STATIC);
builder.addField(TransactionReceipt.Logs.class, "log", Modifier.PUBLIC);
for (NamedTypeName namedType : indexedParameters) {
TypeName typeName = getEventNativeType(namedType.typeName);
builder.addField(typeName, namedType.getName(), Modifier.PUBLIC);
}
for (NamedTypeName namedType : nonIndexedParameters) {
TypeName typeName = getNativeType(namedType.typeName);
builder.addField(typeName, namedType.getName(), Modifier.PUBLIC);
}
return builder.build();
}
private MethodSpec buildSubscribeEventFunction(String eventName) throws ClassNotFoundException {
String generatedFunctionName =
"subscribe" + StringUtils.capitaliseFirstLetter(eventName) + "Event";
MethodSpec.Builder getEventMethodBuilder =
MethodSpec.methodBuilder(generatedFunctionName)
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, FROM_BLOCK)
.addParameter(String.class, TO_BLOCK);
addParameter(getEventMethodBuilder, "string[]", OTHER_TOPICS);
getEventMethodBuilder.addParameter(EventCallback.class, CALLBACK_VALUE);
getEventMethodBuilder.addStatement(
"String topic0 = $N.encode(" + buildEventDefinitionName(eventName) + ")",
EVENT_ENCODER);
getEventMethodBuilder.addStatement(
"subscribeEvent(ABI,BINARY"
+ ","
+ "topic0"
+ ","
+ FROM_BLOCK
+ ","
+ TO_BLOCK
+ ","
+ OTHER_TOPICS
+ ","
+ CALLBACK_VALUE
+ ")");
return getEventMethodBuilder.build();
}
private MethodSpec buildDefaultSubscribeEventLog(String eventName) {
String generatedFunctionName =
"subscribe" + StringUtils.capitaliseFirstLetter(eventName) + "Event";
MethodSpec.Builder getEventMethodBuilder =
MethodSpec.methodBuilder(generatedFunctionName)
.addModifiers(Modifier.PUBLIC)
.addParameter(EventCallback.class, CALLBACK_VALUE);
getEventMethodBuilder.addStatement(
"String topic0 = $N.encode(" + buildEventDefinitionName(eventName) + ")",
EVENT_ENCODER);
getEventMethodBuilder.addStatement(
"subscribeEvent(ABI,BINARY" + ",topic0" + "," + CALLBACK_VALUE + ")");
return getEventMethodBuilder.build();
}
private MethodSpec buildEventTransactionReceiptFunction(
String responseClassName,
String functionName,
List indexedParameters,
List nonIndexedParameters) {
ParameterizedTypeName parameterizedTypeName =
ParameterizedTypeName.get(
ClassName.get(List.class), ClassName.get("", responseClassName));
String generatedFunctionName =
"get" + StringUtils.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();
}
private List buildEventFunctions(
ABIDefinition functionDefinition, TypeSpec.Builder classBuilder)
throws ClassNotFoundException {
String functionName = functionDefinition.getName();
List inputs = functionDefinition.getInputs();
String responseClassName =
StringUtils.capitaliseFirstLetter(functionName) + "EventResponse";
List parameters = new ArrayList<>();
List indexedParameters = new ArrayList<>();
List nonIndexedParameters = new ArrayList<>();
Integer index = 0;
Set eventParamNameFilter = new HashSet<>();
for (ABIDefinition.NamedType namedType : inputs) {
if (namedType.getName() != null && !namedType.getName().equals("")) {
eventParamNameFilter.add(namedType.getName());
}
}
for (ABIDefinition.NamedType namedType : inputs) {
if (namedType.getName() == null || namedType.getName().equals("")) {
String paramName = functionName + "Param" + index;
while (eventParamNameFilter.contains(paramName)) {
index++;
paramName = functionName + "Param" + index;
}
eventParamNameFilter.add(paramName);
namedType.setName(paramName);
}
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(buildSubscribeEventFunction(functionName));
methods.add(buildDefaultSubscribeEventLog(functionName));
return methods;
}
private CodeBlock buildTypedResponse(
String objectName,
List indexedParameters,
List nonIndexedParameters,
boolean flowable) {
String nativeConversion;
nativeConversion = ".getValue()";
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(),
getEventNativeType(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(),
getNativeType(nonIndexedParameters.get(i).getTypeName()),
i);
}
return builder.build();
}
protected 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.sdk.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(getNativeType(typeName));
}
return result;
}
private static void buildVariableLengthReturnFunctionConstructor(
MethodSpec.Builder methodBuilder,
String functionName,
String inputParameters,
List outputParameterTypes) {
List