Skip to content

Commit

Permalink
Merge pull request hyperledger-web3j#228 from eepstein/master
Browse files Browse the repository at this point in the history
Support codegen directly from Truffle json contract files
  • Loading branch information
conor10 authored Nov 12, 2017
2 parents 1b07e85 + 8db2092 commit ec2d3ca
Show file tree
Hide file tree
Showing 24 changed files with 2,575 additions and 79 deletions.
21 changes: 20 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ Features
- Ethereum wallet support
- Auto-generation of Java smart contract wrappers to create, deploy, transact with and call smart
contracts from native Java code
- from solc output; or
- from Truffle `.json` contract files.
- Reactive-functional API for working with filters
- Support for Parity's
`Personal <https://github.com/paritytech/parity/wiki/JSONRPC-personal-module>`__, and Geth's
Expand Down Expand Up @@ -251,7 +253,24 @@ Now you can create and deploy your smart contract:
GAS_PRICE, GAS_LIMIT,
<param1>, ..., <paramN>).send(); // constructor params
Or use an existing contract:
Alternatively, if you use `Truffle <http://truffleframework.com/>`_, you can make use of its `.json` output files:

.. code-block:: bash
# Inside your Truffle project
$ truffle compile
$ truffle deploy
Then generate the wrapper code using web3j's `Command line tools`_:

.. code-block:: bash
$ cd /path/to/your/web3j/java/project
$ web3j truffle generate /path/to/<truffle-smart-contract-output>.json -o /path/to/src/main/java -p com.your.organisation.name
Whether using `Truffle` or `solc` directly, either way you get a ready-to-use Java wrapper for your contract.

So, to use an existing contract:

.. code-block:: java
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.web3j.codegen;

import java.io.File;

import static org.web3j.codegen.Console.exitError;

/**
* Abstract base class for the concrete function wrapper generators.
*/
abstract class FunctionWrapperGenerator {

static final String JAVA_TYPES_ARG = "--javaTypes";
static final String SOLIDITY_TYPES_ARG = "--solidityTypes";

final File destinationDirLocation;
final String basePackageName;
final boolean useJavaNativeTypes;

FunctionWrapperGenerator(
String destinationDirLocation,
String basePackageName,
boolean useJavaNativeTypes) {

this.destinationDirLocation = new File(destinationDirLocation);
this.basePackageName = basePackageName;
this.useJavaNativeTypes = useJavaNativeTypes;
}

static boolean useJavaNativeTypes(String argVal, String usageString) {
boolean useJavaNativeTypes = true;
if (SOLIDITY_TYPES_ARG.equals(argVal)) {
useJavaNativeTypes = false;
} else if (JAVA_TYPES_ARG.equals(argVal)) {
useJavaNativeTypes = true;
} else {
exitError(usageString);
}
return useJavaNativeTypes;
}

static String parsePositionalArg(String[] args, int idx) {
if (args != null && args.length > idx) {
return args[idx];
} else {
return "";
}
}

static String parseParameterArgument(String[] args, String... parameters) {
for (String parameter : parameters) {
for (int i = 0; i < args.length; i++) {
if (args[i].equals(parameter)
&& i + 1 < args.length) {
String parameterValue = args[i + 1];
if (!parameterValue.startsWith("-")) {
return parameterValue;
}
}
}
}
return "";
}

static String getFileNameNoExtension(String fileName) {
String[] splitName = fileName.split("\\.(?=[^.]*$)");
return splitName[0];
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
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;

Expand Down Expand Up @@ -81,24 +83,74 @@ 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<AbiDefinition> abi, String destinationDir,
String basePackageName, Map<String, String> addresses)
throws IOException, ClassNotFoundException {
String className = Strings.capitaliseFirstLetter(contractName);

TypeSpec.Builder classBuilder = createClassBuilder(className, bin);

classBuilder.addMethod(buildConstructor(Credentials.class, CREDENTIALS));
classBuilder.addMethod(buildConstructor(TransactionManager.class, TRANSACTION_MANAGER));
classBuilder.addMethods(
buildFunctionDefinitions(className, classBuilder, loadContractDefinition(abi)));
buildFunctionDefinitions(className, classBuilder, abi));
classBuilder.addMethod(buildLoad(className, Credentials.class, CREDENTIALS));
classBuilder.addMethod(buildLoad(className, TransactionManager.class, TRANSACTION_MANAGER));

addAddressesSupport(classBuilder, addresses);

write(basePackageName, classBuilder.build(), destinationDir);
}

private void addAddressesSupport(TypeSpec.Builder classBuilder, Map<String, String> 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.web3j.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);

}
}


private TypeSpec.Builder createClassBuilder(String className, String binary) {

String javadoc = CODEGEN_WARNING + getWeb3jVersion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,16 @@
/**
* Java wrapper source code generator for Solidity ABI format.
*/
public class SolidityFunctionWrapperGenerator {
public class SolidityFunctionWrapperGenerator extends FunctionWrapperGenerator {

private static final String USAGE = "solidity generate "
+ "[--javaTypes|--solidityTypes] "
+ "<input binary file>.bin <input abi file>.abi "
+ "-p|--package <base package name> "
+ "-o|--output <destination base directory>";

static final String JAVA_TYPES_ARG = "--javaTypes";
static final String SOLIDITY_TYPES_ARG = "--solidityTypes";

private String binaryFileLocation;
private String absFileLocation;
private File destinationDirLocation;
private String basePackageName;
private boolean useJavaNativeTypes;
private final String binaryFileLocation;
private final String absFileLocation;

private SolidityFunctionWrapperGenerator(
String binaryFileLocation,
Expand All @@ -42,11 +36,9 @@ private SolidityFunctionWrapperGenerator(
String basePackageName,
boolean useJavaNativeTypes) {

super(destinationDirLocation, basePackageName, useJavaNativeTypes);
this.binaryFileLocation = binaryFileLocation;
this.absFileLocation = absFileLocation;
this.destinationDirLocation = new File(destinationDirLocation);
this.basePackageName = basePackageName;
this.useJavaNativeTypes = useJavaNativeTypes;
}

public static void run(String[] args) throws Exception {
Expand All @@ -72,14 +64,7 @@ public static void main(String[] args) throws Exception {
exitError(USAGE);
}

boolean useJavaNativeTypes = true;
if (fullArgs[0].equals(SOLIDITY_TYPES_ARG)) {
useJavaNativeTypes = false;
} else if (fullArgs[0].equals(JAVA_TYPES_ARG)) {
useJavaNativeTypes = true;
} else {
exitError(USAGE);
}
boolean useJavaNativeTypes = useJavaNativeTypes(fullArgs[0], USAGE);

String binaryFileLocation = parsePositionalArg(fullArgs, 1);
String absFileLocation = parsePositionalArg(fullArgs, 2);
Expand All @@ -102,27 +87,11 @@ public static void main(String[] args) throws Exception {
.generate();
}

private static String parsePositionalArg(String[] args, int idx) {
if (args != null && args.length > idx) {
return args[idx];
} else {
return "";
}
}

private static String parseParameterArgument(String[] args, String... parameters) {
for (String parameter : parameters) {
for (int i = 0; i < args.length; i++) {
if (args[i].equals(parameter)
&& i + 1 < args.length) {
String parameterValue = args[i + 1];
if (!parameterValue.startsWith("-")) {
return parameterValue;
}
}
}
}
return "";
static List<AbiDefinition> loadContractDefinition(File absFile)
throws IOException {
ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
AbiDefinition[] abiDefinition = objectMapper.readValue(absFile, AbiDefinition[].class);
return Arrays.asList(abiDefinition);
}

private void generate() throws IOException, ClassNotFoundException {
Expand Down Expand Up @@ -157,15 +126,5 @@ private void generate() throws IOException, ClassNotFoundException {
}
}

private static List<AbiDefinition> loadContractDefinition(File absFile)
throws IOException {
ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
AbiDefinition[] abiDefinition = objectMapper.readValue(absFile, AbiDefinition[].class);
return Arrays.asList(abiDefinition);
}

static String getFileNameNoExtension(String fileName) {
String[] splitName = fileName.split("\\.(?=[^.]*$)");
return splitName[0];
}
}
Loading

0 comments on commit ec2d3ca

Please sign in to comment.