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