blob: 584b33d3576005616c00d2e17fa10d4ed267873c [file] [log] [blame]
/*
* Copyright (c) Robert Bosch GmbH. All rights reserved.
*/
package org.eclipse.blockchain.ui.views;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.blockchain.core.BlockchainCore;
import org.eclipse.blockchain.core.CoreCommandExecutor;
import org.eclipse.blockchain.core.EthereumProject;
import org.eclipse.blockchain.core.EthereumProjectNature;
import org.eclipse.blockchain.core.ProjectCreator;
import org.eclipse.blockchain.core.Web3jHandler;
import org.eclipse.blockchain.ui.Activator;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.fx.ui.workbench3.FXViewPart;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.ISources;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.services.IEvaluationService;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.control.Separator;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.ToolBar;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
/**
* @author DMU1COB
*/
public class TransactionsViewPart extends FXViewPart implements IResourceChangeListener {
private static final String SOLIDITY_OUTPUT_FOLDER = "sol-output";
static Map<String, Set<IResource>> projectToSolidityFilesMap = new HashMap<>();
ComboBox<String> cmbSmartContracts = new ComboBox<>();
List<Object> listOfObjects = new ArrayList<>();
GridPane gridPane = new GridPane();
TextField constructorArgs = new TextField();
private String anyErrors = "Not Done";
private String deploySmartContractMessage = "Not Done";
private String invokeSmartContractMessage = "Not Done";
/**
* {@inheritDoc}
*/
@Override
public void init(final IViewSite site) throws PartInitException {
super.init(site);
ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
}
/**
* {@inheritDoc}
*/
@Override
public void resourceChanged(final IResourceChangeEvent event) {
if ((event.getDelta() != null)) {
IResourceDelta rootDelta = event.getDelta();
try {
rootDelta.accept(new IResourceDeltaVisitor() {
@Override
public boolean visit(final IResourceDelta delta) throws CoreException {
if ((delta != null) && (delta.getResource() != null) && (delta.getResource() instanceof IFile) &&
(delta.getResource().getFileExtension() != null) &&
(delta.getResource().getFileExtension().equals("sol")) && (delta.getKind() == IResourceDelta.REMOVED)) {
Object currentSelection = getCurrentSelectionProject();
if ((currentSelection != null) &&
projectToSolidityFilesMap.containsKey(((IProject) currentSelection).getName())) {
projectToSolidityFilesMap.get(((IProject) currentSelection).getName()).remove(delta.getResource());
Display.getDefault().syncExec(() -> {
updateSmartContractsCombo((IProject) currentSelection);
});
}
}
return true;
}
});
}
catch (CoreException e) {
BlockchainCore.getInstance().logException(Activator.PLUGIN_ID, e.getMessage(), e);
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected Scene createFxScene() {
Scene scene = null;
try {
this.gridPane.setPadding(new Insets(20));
this.gridPane.setHgap(10);
this.gridPane.setVgap(10);
ToolBar toolBar = new ToolBar();
Button btnCompile = new Button("Compile");
toolBar.getItems().add(btnCompile);
toolBar.getItems().add(new Separator());
Button btnDeploy = new Button("Deploy");
toolBar.getItems().add(btnDeploy);
toolBar.getItems().add(new Separator());
Button btnContractAddress = new Button("CxAddr");
btnContractAddress.setTooltip(new Tooltip("Contract Address"));
toolBar.getItems().add(btnContractAddress);
toolBar.setOrientation(Orientation.HORIZONTAL);
ProgressBar progressBar = new ProgressBar(0);
progressBar.setProgress(0.5);
Label lblContract = new Label();
lblContract.setText("Smart Contract");
this.cmbSmartContracts.setPrefWidth(200);
ObservableList<String> smartContractList = FXCollections.observableArrayList();
smartContractList.addAll(getSmartContracts((IProject) getCurrentSelectionProject()));
this.cmbSmartContracts.setItems(smartContractList);
this.cmbSmartContracts.getSelectionModel().selectFirst();
// Add a ChangeListener to the ComboBox
this.cmbSmartContracts.getSelectionModel().selectedItemProperty()
.addListener((final ObservableValue<? extends String> ov, final String oldValue, final String newValue) -> {
// on change of smart-contract load that corresponding details in the UI
this.gridPane.getChildren().remove(this.constructorArgs);
constructorTextUI();
clearWidgets();
String solPath = getSolPath(newValue);
if (!solPath.isEmpty()) {
String projectName = solPath.split("--")[1];
String scAbsPath = solPath.split("--")[0];
EthereumProject ethProject = ProjectCreator.getInstance().getEthProject(projectName);
if (ethProject.isSCDeployed(ethProject.getProject()
.getFile(scAbsPath.replace(ethProject.getProject().getLocation().toOSString(), "")))) {
constructUI(solPath);
}
}
});
// Contract Address
btnContractAddress.setOnAction(event -> {
String selectedContract = this.cmbSmartContracts.getSelectionModel().getSelectedItem();
String solPath = getSolPath(selectedContract);
String projectName = solPath.split("--")[1];
String scAbsPath = solPath.split("--")[0];
EthereumProject ethProject = ProjectCreator.getInstance().getEthProject(projectName);
IFile file =
ethProject.getProject().getFile(scAbsPath.replace(ethProject.getProject().getLocation().toOSString(), ""));
String contractAddr = "Not deployed!!!";
if (ethProject.isSCDeployed(file)) {
contractAddr = file.getName() + "-" + ethProject.getContractAddress(file);
}
Alert alert = new Alert(AlertType.INFORMATION);
alert.setTitle("Contract Address");
TextArea detailsArea = new TextArea(contractAddr);
detailsArea.setEditable(false);
detailsArea.setWrapText(true);
GridPane.setVgrow(detailsArea, Priority.ALWAYS);
GridPane.setHgrow(detailsArea, Priority.ALWAYS);
alert.getDialogPane().setExpandableContent(detailsArea);
alert.getDialogPane().setExpanded(true);
alert.showAndWait();
});
// Deploy
btnDeploy.setOnAction(value -> {
clearWidgets();
String selectedContract = this.cmbSmartContracts.getSelectionModel().getSelectedItem();
try {
String solPath = getSolPath(selectedContract);
this.deploySmartContractMessage = "Not Done";
IRunnableWithProgress deployRunnable = new IRunnableWithProgress() {
@Override
public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
TransactionsViewPart.this.deploySmartContractMessage =
Web3jHandler.getInstance().deploySmartContract(solPath.split("--")[1], selectedContract,
getOutputDirPath(selectedContract), solPath.split("--")[0],
TransactionsViewPart.this.constructorArgs.getText(), EtherAccountViewPart.getSelectedItem());
}
catch (Exception e) {
BlockchainCore.getInstance().logException(Activator.PLUGIN_ID, e.getMessage(), e);
}
}
};
new ProgressMonitorDialog(Display.getDefault().getActiveShell()).run(true, false, deployRunnable);
if (this.deploySmartContractMessage.isEmpty()) {
constructUI(solPath);
}
else {
MessageDialog errorDialog = new MessageDialog(Display.getDefault().getActiveShell(), "Geth Server error",
null, this.deploySmartContractMessage, MessageDialog.ERROR, new String[] { "OK" }, 0);
errorDialog.open();
}
// If-end
}
catch (Exception e) {
BlockchainCore.getInstance().logException(Activator.PLUGIN_ID, e.getMessage(), e);
}
});
// Compile
btnCompile.setOnAction(value -> {
Object selection = getCurrentSelectionResource();
if ((selection != null) && (selection instanceof IResource)) {
IProject selectedProject = ((IResource) selection).getProject();
this.gridPane.getChildren().remove(this.constructorArgs);
clearWidgets();
Set<IResource> solidityFilesSet =
projectToSolidityFilesMap.computeIfAbsent(selectedProject.getName(), s -> new HashSet<>());
try {
if (selection instanceof IProject) {
/**
* The existing list of solidity files should be cleared only if project level compilation is choosen,
* else if single file is selected for compilation only add that file to the list
*/
solidityFilesSet.clear();
selectedProject.accept((final IResource resource) -> {
if ((resource instanceof IFile) && (resource.getFileExtension() != null) &&
resource.getFileExtension().equals("sol")) {
solidityFilesSet.add(resource);
}
return true;
});
}
else if ((((IResource) selection).getFileExtension() != null) &&
(((IResource) selection).getFileExtension().equals("sol"))) {
solidityFilesSet.add((IResource) selection);
}
if ((selection instanceof IProject) && selectedProject.getFolder(SOLIDITY_OUTPUT_FOLDER).exists()) {
selectedProject.getFolder(SOLIDITY_OUTPUT_FOLDER).delete(true, new NullProgressMonitor());
}
else if (!selectedProject.getFolder(SOLIDITY_OUTPUT_FOLDER).exists()) {
selectedProject.getFolder(SOLIDITY_OUTPUT_FOLDER).create(true, true, new NullProgressMonitor());
}
this.anyErrors = "Not Done";
IRunnableWithProgress compileRunnable = new IRunnableWithProgress() {
@Override
public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
List<IResource> solFiles = new ArrayList<>();
if (selection instanceof IProject) {
solFiles.addAll(solidityFilesSet);
}
else {
// Single sol file
solFiles.add((IResource) selection);
}
TransactionsViewPart.this.anyErrors = CoreCommandExecutor.getInstance().solidityCompile(
selectedProject.getName(), solFiles, selectedProject.findMember(SOLIDITY_OUTPUT_FOLDER));
selectedProject.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
}
catch (CoreException | IOException | InterruptedException | ClassNotFoundException e) {
BlockchainCore.getInstance().logException(Activator.PLUGIN_ID, e.getMessage(), e);
}
}
};
if (Arrays.asList(selectedProject.getDescription().getNatureIds())
.contains(EthereumProjectNature.ETHEREUM_NATURE)) {
new ProgressMonitorDialog(Display.getDefault().getActiveShell()).run(true, false, compileRunnable);
}
else {
this.anyErrors = "Selected project is not an Ethereum Project";
}
if (!this.anyErrors.isEmpty()) {
MessageDialog errorDialog = new MessageDialog(Display.getDefault().getActiveShell(), "Solc error", null,
this.anyErrors, MessageDialog.ERROR, new String[] { "OK" }, 0);
errorDialog.open();
}
}
catch (CoreException | InvocationTargetException | InterruptedException e) {
BlockchainCore.getInstance().logException(Activator.PLUGIN_ID, e.getMessage(), e);
}
updateSmartContractsCombo(selectedProject);
}
});// End of compile
this.gridPane.add(toolBar, 0, 0);
this.gridPane.add(lblContract, 0, 1);
this.gridPane.add(this.cmbSmartContracts, 1, 1);
toolBar.getItems().add(new Separator());
// gridPane.add(progressBar, 0, 2);
ScrollPane scrollPane = new ScrollPane(this.gridPane);
scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
scene = new Scene(scrollPane, 550, 400);
scene.getStylesheets().add(getClass().getResource("Views.css").toExternalForm());
}
catch (Exception e) {
BlockchainCore.getInstance().logException(Activator.PLUGIN_ID, e.getMessage(), e);
}
return scene;
}
private void constructUI(final String solPath) {
Map<String, String> uiComponentForSolidityFile =
CoreCommandExecutor.getInstance().getUIComponentForSolidityFile(solPath.split("--")[0], false);
if (uiComponentForSolidityFile == null) {
return;
}
Set<Entry<String, String>> entrySet = uiComponentForSolidityFile.entrySet();
Iterator<Entry<String, String>> iterator = entrySet.iterator();
int row = 2;
int col = 0;
while (iterator.hasNext()) {
Entry<String, String> next = iterator.next();
String key = next.getKey();
String[] keyValue = key.split("-");
String value2 = next.getValue();
if (value2.isEmpty()) {
Button btnOK = new Button();
btnOK.setText(keyValue[0]);
Label lblAssign = new Label();
lblAssign.setMaxWidth(150);
btnOK.setOnAction(val -> {
this.invokeSmartContractMessage = "Not Done";
try {
IRunnableWithProgress invokeSCRunnable = new IRunnableWithProgress() {
@Override
public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
TransactionsViewPart.this.invokeSmartContractMessage = Web3jHandler.getInstance().invokeSCFunction(
solPath.split("--")[1], key, solPath.split("--")[0], "", EtherAccountViewPart.getSelectedItem());
}
catch (Exception e) {
BlockchainCore.getInstance().logException(Activator.PLUGIN_ID, e.getMessage(), e);
}
}
};
new ProgressMonitorDialog(Display.getDefault().getActiveShell()).run(true, false, invokeSCRunnable);
Display.getDefault().syncExec(() -> {
EtherAccountViewPart.updateView();
});
}
catch (Exception e) {
BlockchainCore.getInstance().logException(Activator.PLUGIN_ID, e.getMessage(), e);
}
lblAssign.setText(this.invokeSmartContractMessage);
lblAssign.setTooltip(new Tooltip("Click to view in details"));
String localLabelValue = this.invokeSmartContractMessage;
lblAssign.setOnMouseClicked(
m -> MessageDialog.openInformation(Display.getDefault().getActiveShell(), "Value", localLabelValue));
});
this.gridPane.add(btnOK, col, row);
this.gridPane.add(lblAssign, col + 1, row);
row = row + 1;
col = 0;
this.listOfObjects.add(btnOK);
this.listOfObjects.add(lblAssign);
}
else {
Button btnOK = new Button();
btnOK.setText(keyValue[0]);
TextField txtField = new TextField();
txtField.setTooltip(new Tooltip(
"Enter input args as ; separated text and if any array elements separate them with , - expected arguments " +
keyValue[1]));
Label lblAssign = new Label();
lblAssign.setMaxWidth(150);
btnOK.setOnAction(v -> {
this.invokeSmartContractMessage = "Not Done";
try {
IRunnableWithProgress invokeSCRunnable = new IRunnableWithProgress() {
@Override
public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
TransactionsViewPart.this.invokeSmartContractMessage =
Web3jHandler.getInstance().invokeSCFunction(solPath.split("--")[1], key, solPath.split("--")[0],
txtField.getText(), EtherAccountViewPart.getSelectedItem());
}
catch (Exception e) {
BlockchainCore.getInstance().logException(Activator.PLUGIN_ID, e.getMessage(), e);
}
}
};
new ProgressMonitorDialog(Display.getDefault().getActiveShell()).run(true, false, invokeSCRunnable);
Display.getDefault().syncExec(() -> {
EtherAccountViewPart.updateView();
});
}
catch (Exception e) {
BlockchainCore.getInstance().logException(Activator.PLUGIN_ID, e.getMessage(), e);
}
lblAssign.setText(this.invokeSmartContractMessage);
lblAssign.setTooltip(new Tooltip("Click to view in details"));
String localLabelValue = this.invokeSmartContractMessage;
lblAssign.setOnMouseClicked(
m -> MessageDialog.openInformation(Display.getDefault().getActiveShell(), "Value", localLabelValue));
});
this.listOfObjects.add(btnOK);
this.listOfObjects.add(lblAssign);
this.listOfObjects.add(txtField);
this.gridPane.add(btnOK, col, row);
this.gridPane.add(txtField, col + 1, row);
this.gridPane.add(lblAssign, col + 2, row);
row = row + 1;
col = 0;
}
}
}
private Object getCurrentSelectionProject() {
Object variable = PlatformUI.getWorkbench().getService(IEvaluationService.class).getCurrentState()
.getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME);
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
variable = getFirstElement(variable);
return variable instanceof IResource ? ((IResource) variable).getProject()
: (projects.length > 0 ? projects[0] : null);
}
private Object getCurrentSelectionResource() {
Object variable = PlatformUI.getWorkbench().getService(IEvaluationService.class).getCurrentState()
.getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME);
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
variable = getFirstElement(variable);
return variable instanceof IResource ? ((IResource) variable) : (projects.length > 0 ? projects[0] : null);
}
private Object getFirstElement(final Object variable) {
if ((variable instanceof ISelection) && (variable instanceof IStructuredSelection)) {
Object firstElement = ((IStructuredSelection) variable).getFirstElement();
if (firstElement instanceof IResource) {
return firstElement;
}
}
return null;
}
/**
* This returns solidity file path along with the project name to which the solidity file belongs to.
*
* @param selectedContract
* @return
*/
private String getSolPath(final String selectedContract) {
Set<IResource> solFiles = (Set<IResource>) this.cmbSmartContracts.getUserData();
if (solFiles != null) {
for (IResource sol : solFiles) {
if (sol.getName().replace(".sol", "").equals(selectedContract)) {
return sol.getLocation().toOSString() + "--" + sol.getProject().getName();
}
}
}
return "";
}
private String getOutputDirPath(final String selectedContract) {
Set<IResource> solFiles = (Set<IResource>) this.cmbSmartContracts.getUserData();
for (IResource sol : solFiles) {
if (sol.getName().replace(".sol", "").equals(selectedContract)) {
return sol.getProject().findMember(SOLIDITY_OUTPUT_FOLDER).getLocation().toOSString();
}
}
return "";
}
private void clearWidgets() {
if (!this.listOfObjects.isEmpty()) {
for (Object obj : this.listOfObjects) {
this.gridPane.getChildren().remove(obj);
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected void setFxFocus() {
// TODO Auto-generated method stub
}
private static Set<String> getSmartContracts(final IProject project) {
if (project == null) {
return Collections.emptySet();
}
Set<IResource> solFiles = projectToSolidityFilesMap.computeIfAbsent(project.getName(), s -> new HashSet<>());
return solFiles.stream().map(IResource::getName).map(r -> r.replace(".sol", "")).collect(Collectors.toSet());
}
private void updateSmartContractsCombo(final IProject project) {
if (this.cmbSmartContracts != null) {
ObservableList<String> smartContractList = FXCollections.observableArrayList();
smartContractList.addAll(getSmartContracts(project));
this.cmbSmartContracts.setItems(smartContractList);
this.cmbSmartContracts.getSelectionModel().selectFirst();
this.cmbSmartContracts.setUserData(projectToSolidityFilesMap.get(project.getName()));
constructorTextUI();
}
}
private void constructorTextUI() {
String solPath = getSolPath(this.cmbSmartContracts.getSelectionModel().getSelectedItem()).split("--")[0];
Map<String, String> uiComponentForSolidityFile =
CoreCommandExecutor.getInstance().getUIComponentForSolidityFile(solPath, true);
Set<Entry<String, String>> entrySet = uiComponentForSolidityFile.entrySet();
Iterator<Entry<String, String>> iterator = entrySet.iterator();
Entry<String, String> next = iterator.hasNext() ? iterator.next() : null;
if ((next != null) && !next.getValue().equals("")) {
this.gridPane.add(this.constructorArgs, 1, 0);
this.constructorArgs.setTooltip(new Tooltip(
"Enter input args as ; separated text and if any array elements separate them with , - expected arguments " +
next.getValue()));
}
}
}