/* | |
* 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())); | |
} | |
} | |
} |