blob: a2c49530678207d79bcb490b34264aa4216df916 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 RBEI and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v. 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Adhith Gopal - Initial API and Implementation
* Santhosh Gokhale D
*******************************************************************************/
package org.eclipse.blockchain.core;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.blockchain.core.events.BlockchainEnvironmentChangedTrigger;
import org.eclipse.blockchain.core.events.IBlockchainEnvironmentChangedEvent;
import org.eclipse.blockchain.core.events.IBlockchainEnvironmentChangedListener;
import org.eclipse.blockchain.model.core.EthereumProjectHandler;
import org.eclipse.blockchain.model.core.EthereumProjectModelHandler;
import org.eclipse.blockchain.model.ethproject.EthereumProject;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.ICommand;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
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.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.InstanceScope;
/**
* Project handle for Ethereum Project
*/
public class ProjectCreator implements IResourceChangeListener, IBlockchainEnvironmentChangedListener {
private static ProjectCreator instance;
private final Map<String, EthereumProject> projectMap = new HashMap<>();
private String selectedEnvironment = "";
private ProjectCreator() {
}
/**
* There will be only 1 Project creator instance which can be used to create
* multiple Ethereum projects
*
* @return - The Project creator instance
*/
public static ProjectCreator getInstance() {
if (instance == null) {
instance = new ProjectCreator();
ResourcesPlugin.getWorkspace().addResourceChangeListener(instance);
BlockchainEnvironmentChangedTrigger.getInstance().addBlockchainEnvironmentChangedListener(instance);
}
return instance;
}
/**
* This API is used to create an Ethereum project inside the workspace
*
* @param projectName - Name of the project
* @param projectPath
* @return - Project creation message if error occurs then it will return the
* error message else empty
* @throws IOException - If any problem with file access then this will be
* thrown
* @throws CoreException -
*/
public String createNewProject(final String projectName, final String projectPath)
throws IOException, CoreException {
if (this.projectMap.containsKey(projectName)) {
return "A project with same name already exists in the workspace";
}
// EthereumProject ethProject = new EthereumProject(projectPath + File.separator
// + projectName);
EthereumProject ethProject = EthereumProjectHandler.getInstance().getEthProject(projectName);
ethProject.setProjectLocation(projectPath + File.separator + projectName);
String directoryCreation = createFolderInWorkspaceForNewProject(ethProject);
if (directoryCreation.equals("")) {
// Contracts folder and sample contract
File contractsFolder = new File(ethProject.getProjectLocation() + File.separator + "contracts");
contractsFolder.mkdir();
File sampleSolFile = new File(contractsFolder.getAbsolutePath() + File.separator + "HelloWorld.sol");
if (!sampleSolFile.createNewFile()) {
throw new IOException(sampleSolFile.getPath());
}
try (BufferedReader br = new BufferedReader(
new InputStreamReader(CoreCommandExecutor.class.getResourceAsStream("HelloWorld.template")));
BufferedWriter bw = new BufferedWriter(new FileWriter(sampleSolFile));) {
String sampleSolContent = "";
while ((sampleSolContent = br.readLine()) != null) {
bw.append(sampleSolContent);
bw.append(System.lineSeparator());
}
}
configureAndImportProject(ethProject, projectName, URIUtil.toURI(ethProject.getProjectLocation()));
this.projectMap.put(projectName, ethProject);
}
return directoryCreation;
}
/**
* To check whether the project has ethereum nature before importing into
* workspace
*
* @param projectPathString
* @return
*/
public String isItAnEthereumProject(final String projectPathString) {
// Check if sucha directory exists
File projectDirectory = new File(projectPathString);
if (projectDirectory.exists()) {
// valid directory
String[] splitProjPath = projectPathString.split("\\\\");
if (splitProjPath.length >= 2) {
// check if there is a .project in the location
File projDescriptorFile = new File(projectPathString + File.separator + ".project");
if (projDescriptorFile.exists()) {
IProjectDescription description;
try {
Path projectIPath = new Path(projectPathString);
IWorkspace workspace = ResourcesPlugin.getWorkspace();
description = workspace.loadProjectDescription(
projectIPath.append(IPath.SEPARATOR + IProjectDescription.DESCRIPTION_FILE_NAME));
String[] natureIds = description.getNatureIds();
for (String natureID : natureIds) {
if ((EthereumNature.ETHEREUM_NATURE.equalsIgnoreCase(natureID))) {
return "";
}
}
return "Not Ethereum project";
} catch (CoreException e) {
BlockchainCore.getInstance().logException("", e.getMessage(), e);
}
}
return "No Ethereum project exists at: " + projectPathString;
}
return "Invalid directory path: " + projectPathString;
}
return "Directory " + projectPathString + " does not exist";
}
/**
* @param projectPathString
* @return
*/
public String createExistingProject(final String projectPathString) {
// Check if sucha directory exists
File projectDirectory = new File(projectPathString);
if (projectDirectory.exists()) {
// valid directory
String[] splitProjPath = projectPathString.split("\\\\");
if (splitProjPath.length >= 2) {
String projName = splitProjPath[splitProjPath.length - 1];
EthereumProject ethProject = EthereumProjectHandler.getInstance().getEthProject(projName);
ethProject.setProjectLocation(projectPathString);
// check if there is a .project in the location
File projDescriptorFile = new File(projectPathString + File.separator + ".project");
EthereumProject ethProjectFromModal = EthereumProjectModelHandler.getInstance()
.loadProject(new File(projectPathString + File.separator + ".ethproject").getAbsolutePath());
if (projDescriptorFile.exists()) {
/**
* Populate modal data into EthProject
*/
ethProject.setFirstMatchingSolFile(ethProjectFromModal.getFirstMatchingSolFile());
ethProject.setCompiledSolidityFiles(ethProjectFromModal.getCompiledSolidityFiles());
ethProject.setEnvironmentBasedDeployedModel(ethProjectFromModal.getEnvironmentBasedDeployedModel());
ethProject.getDeploymentmodel().addAll(ethProjectFromModal.getDeploymentmodel());
return invokeProjectCreationWithDescription(projectPathString, projectDirectory, projName,
ethProject);
}
return "No Ethereum project exists at: " + projectPathString;
}
return "Invalid directory path: " + projectPathString;
}
// directory doesn't exist. Should we create?
return "Directory " + projectPathString + " does not exist";
}
/**
* @param projectPathString
* @param projectDirectory
* @param projName
* @param ethProject
* @return
*/
private String invokeProjectCreationWithDescription(final String projectPathString, final File projectDirectory,
final String projName, final EthereumProject ethProject) {
// project exists at this location
IProgressMonitor monitor = new NullProgressMonitor();
Path projectIPath = new Path(projectPathString);
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IProjectDescription description;
try {
description = workspace.loadProjectDescription(
projectIPath.append(IPath.SEPARATOR + IProjectDescription.DESCRIPTION_FILE_NAME));
// String[] natureIds = description.getNatureIds();
// boolean hasEthereumNature = false;
// for (String natureID : natureIds) {
// if
// ((EthereumProjectNature.ETHEREUM_NATURE.equalsIgnoreCase(natureID)))
// {
// hasEthereumNature = true;
// break;
// }
// }
// if (!hasEthereumNature) {
// String[] newNatureIds = new String[natureIds.length + 1];
// for (int z = 0; z < natureIds.length; z++) {
// newNatureIds[z] = natureIds[z];
// }
// newNatureIds[newNatureIds.length - 1] =
// EthereumProjectNature.ETHEREUM_NATURE;
// description.setNatureIds(newNatureIds);
// }
createProjectWithDescription(projectDirectory, projName, ethProject, monitor, projectIPath, workspace,
description);
} catch (CoreException e) {
BlockchainCore.getInstance().logException(BlockchainCore.PLUGIN_ID, e.getMessage(), e);
return "Exception occured during project creation";
}
// Should we check if the project has at least one solc file? If not
// create contracts folder and add file?
return "";
}
/**
* @param projectDirectory
* @param projName
* @param ethProject
* @param monitor
* @param projectIPath
* @param workspace
* @param description
* @throws CoreException
*/
private void createProjectWithDescription(final File projectDirectory, final String projName,
final EthereumProject ethProject, final IProgressMonitor monitor, final Path projectIPath,
final IWorkspace workspace, final IProjectDescription description) throws CoreException {
IProject project;
if (!description.getName().equals(projectDirectory.getName())) {
project = workspace.getRoot().getProject(projectDirectory.getName());
} else {
project = workspace.getRoot().getProject(description.getName());
}
if (Platform.getLocation().isPrefixOf(projectIPath)) {
description.setLocation(null);
} else {
description.setLocation(projectIPath);
}
ethProject.setProjectDescription(description);
ethProject.setProject(project);
boolean isProjectExists = project.exists();
if (!isProjectExists) {
project.create(description, 0, monitor);
}
project.open(monitor);
if (!project.getDescription().hasNature(EthereumNature.ETHEREUM_NATURE)) {
IProjectDescription description2 = project.getDescription();
String[] natureIds = description2.getNatureIds();
String[] newNatureIds = new String[natureIds.length + 1];
for (int z = 0; z < natureIds.length; z++) {
newNatureIds[z] = natureIds[z];
}
newNatureIds[newNatureIds.length - 1] = EthereumNature.ETHEREUM_NATURE;
description2.setNatureIds(newNatureIds);
project.setDescription(description2, monitor);
}
if (isProjectExists) {
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
}
this.projectMap.put(projName, ethProject);
}
/**
* @param projectName -
* @return -
* @throws CoreException -
*/
public IFile getFirstMatchingSolFile(final String projectName) throws CoreException {
return EthereumProjectHandler.getInstance().getSolFile(projectName);
}
/**
* This is used to configure and import a ethereum project into prduct
*
* @param projectName - Project name
* @param projectURI - The location URI of the project contents
* @throws CoreException -
* @throws IOException -
*/
private void configureAndImportProject(final EthereumProject ethProject, final String projectName,
final URI projectURI) throws CoreException, IOException {
ethProject.setProjectDescription(ResourcesPlugin.getWorkspace().newProjectDescription(projectName));
ethProject.getProjectDescription().setLocationURI(projectURI);
// ethProject.addDefaultNatures();
EthereumProjectHandler.getInstance().addProjectNature(ethProject, EthereumNature.ETHEREUM_NATURE);
// ethProject.addDefaultBuilders();
EthereumProjectHandler.getInstance().addProjectBuilder(ethProject, EthereumBuilder.ETHEREUM_BUILDER);
for (String builderId : ethProject.getProjectBuilders()) {
addBuildersToProject(ethProject.getProjectDescription(), builderId);
}
// Validate and set nature
String[] natureIds = ethProject.getProjectDescription().getNatureIds();
String[] newNatures = new String[natureIds.length + ethProject.getProjectNatures().size()];
int i = natureIds.length;
for (String n : ethProject.getProjectNatures()) {
newNatures[i++] = n;
}
if (IStatus.OK == ResourcesPlugin.getWorkspace().validateNatureSet(newNatures).getCode()) {
ethProject.getProjectDescription().setNatureIds(newNatures);
} else {
BlockchainCore.getInstance().logException("", "Problem configuring natures", new Exception(Arrays.toString(
ethProject.getProjectNatures().toArray(new String[ethProject.getProjectNatures().size()]))));
}
ethProject.setProject(ResourcesPlugin.getWorkspace().getRoot().getProject(projectName));
ethProject.getProject().create(ethProject.getProjectDescription(), new NullProgressMonitor());
ethProject.getProject().open(new NullProgressMonitor());
createPackageJsonDepFile(ethProject);
}
private void addBuildersToProject(final IProjectDescription projectDescription, final String builderId) {
ICommand[] commands = projectDescription.getBuildSpec();
for (ICommand command : commands) {
if (builderId.equals(command.getBuilderName())) {
return;
}
}
ICommand[] newCommands = new ICommand[commands.length + 1];
System.arraycopy(commands, 0, newCommands, 0, commands.length);
ICommand newCommand = projectDescription.newCommand();
newCommand.setBuilderName(builderId);
newCommands[newCommands.length - 1] = newCommand;
projectDescription.setBuildSpec(newCommands);
}
private String createFolderInWorkspaceForNewProject(final EthereumProject ethProject) {
File projectDirectory = new File(ethProject.getProjectLocation());
if (projectDirectory.mkdir()) {
return "";
}
return "Some Problem in directory creation, please check the location " + ethProject.getProjectLocation();
}
private void createPackageJsonDepFile(final EthereumProject ethProject) throws IOException {
File packageJson = new File(
ethProject.getProject().getLocation().toOSString() + IPath.SEPARATOR + "package.json");
if (!packageJson.createNewFile()) {
throw new IOException(packageJson.getPath());
}
}
/**
* {@inheritDoc} On deletion of project from workspace it should be removed from
* project map as-well.
*/
@Override
public void resourceChanged(final IResourceChangeEvent event) {
IResource resource = event.getResource();
if ((resource != null) && (resource instanceof IProject) && (event.getType() == IResourceChangeEvent.PRE_DELETE)
&& this.projectMap.containsKey(resource.getName())) {
try {
EthereumProjectModelHandler.getInstance().saveProject(this.projectMap.get(resource.getName()));
} catch (IOException e) {
BlockchainCore.getInstance().logException("", e.getMessage(), e);
}
this.projectMap.remove(resource.getName());
EthereumProjectHandler.getInstance().removeEthProject(resource.getName());
} else {
IResourceDelta rootDelta = event.getDelta();
try {
rootDelta.accept(new IResourceDeltaVisitor() {
@Override
public boolean visit(final IResourceDelta delta) throws CoreException {
IResource resource2 = delta.getResource();
if ((resource2 instanceof IFile) && (delta.getResource().getFileExtension() != null)
&& (resource2.getFileExtension().equals("sol"))
&& (delta.getKind() == IResourceDelta.REMOVED)
&& (getEthProject(resource2.getProject().getName()) != null)) {
getEthProject(resource2.getProject().getName()).getCompiledSolidityFiles()
.remove(resource2);
removeDeploymentModelForResource(resource, getSelectedEnvironment());
}
return true;
}
});
} catch (CoreException e) {
BlockchainCore.getInstance().logException("", e.getMessage(), e);
}
}
}
/**
* @param projectName
* @return
*/
public EthereumProject getEthProject(final String projectName) {
return this.projectMap.get(projectName);
}
/**
* This is just a re-direction to EthereumProjectHandler, this is done this way
* because we don't want client's to directly use EthereumProjectHandler
*
* @param solidityFile - The compiled solidity file that belongs to this project
*/
public void addCompiledSolidityFiles(final IResource solidityFile) {
EthereumProjectHandler.getInstance().addCompiledSolidityFiles(solidityFile);
}
/**
* This is just a re-direction to EthereumProjectHandler, this is done this way
* because we don't want client's to directly use EthereumProjectHandler
*
* @param scFile - The SC resource
* @param environment - This denotes the environment into which this SC is
* deployed(Embedded/Geth)
* @return - Whether the SC is deployed into the passed in environment
*/
public boolean isSCDeployed(final IResource scFile, final String environment) {
return EthereumProjectHandler.getInstance().isSCDeployed(scFile, environment);
}
/**
* This is just a re-direction to EthereumProjectHandler, this is done this way
* because we don't want client's to directly use EthereumProjectHandler
*
* @param contractAddress - The contract address of the deployed SC
* @param scFile - The SC resource
* @param lastUsedAccount - The account that was last used for performing a
* transaction
* @param environment - This denotes the environment into which this SC is
* deployed(Embedded/Geth)
*/
public void createDeploymentModel(final String contractAddress, final IResource scFile,
final String lastUsedAccount, final String environment) {
EthereumProjectHandler.getInstance().createDeploymentModel(contractAddress, scFile, lastUsedAccount,
environment);
}
/**
* This is just a re-direction to EthereumProjectHandler, this is done this way
* because we don't want client's to directly use EthereumProjectHandler
*
* @param scFile - The SC resource
* @param environment - This denotes the environment into which this SC is
* deployed(Embedded/Geth)
* @return - The account that was last used for performing a transaction
*/
public String getLastUsedAccount(final IResource scFile, final String environment) {
return EthereumProjectHandler.getInstance().getLastUsedAccount(scFile, environment);
}
/**
* This is just a re-direction to EthereumProjectHandler, this is done this way
* because we don't want client's to directly use EthereumProjectHandler
*
* @param scFile - The SC resource
* @param accountDetails - The new account that was used for transaction
* @param environment - This denotes the environment into which this SC is
* deployed(Embedded/Geth)
*/
public void updateLastUsedAccount(final IResource scFile, final String accountDetails, final String environment) {
EthereumProjectHandler.getInstance().updateLastUsedAccount(scFile, accountDetails, environment);
}
/**
* This is just a re-direction to EthereumProjectHandler, this is done this way
* because we don't want client's to directly use EthereumProjectHandler
*
* @param scFile - The SC resource
* @param environment - This denotes the environment into which this SC is
* deployed(Embedded/Geth)
* @return - The contract address of the deployed SC
*/
public String getContractAddress(final IResource scFile, final String environment) {
return EthereumProjectHandler.getInstance().getContractAddress(scFile, environment);
}
/**
* This is just a re-direction to EthereumProjectHandler, this is done this way
* because we don't want client's to directly use EthereumProjectHandler
*
* @param resource - The SC resource
* @param environment - This denotes the environment into which this SC is
* deployed(Embedded/Geth)
*/
public void removeDeploymentModelForResource(final IResource resource, final String environment) {
EthereumProjectHandler.getInstance().removeDeploymentModelForResource(resource, environment);
}
private String getSelectedEnvironment() {
if (!this.selectedEnvironment.isEmpty()) {
return this.selectedEnvironment;
}
return this.selectedEnvironment = InstanceScope.INSTANCE
.getNode(SecoBlocksPreferenceConstants.SECOBLOCKS_PREF_NODE)
.get(SecoBlocksPreferenceConstants.ENVIRONMENT_PREF_KEY,
SecoBlocksPreferenceConstants.EnvironmentType.EMBEDDED_EVM.toString());
}
/**
* Save all existing workspace project modals
*
* @throws IOException - Any exception while performing I/O
*/
public void saveAllProjects() throws IOException {
EthereumProjectModelHandler.getInstance()
.saveProjects(this.projectMap.values().stream().collect(Collectors.toList()));
}
/**
* {@inheritDoc}
*/
@Override
public void blockchainEnvironmentChanged(final IBlockchainEnvironmentChangedEvent event) {
this.selectedEnvironment = event.getActiveEvironment();
}
}