package bndtools.wizards.project; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.IncrementalProjectBuilder; 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.MultiStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.launching.environments.IExecutionEnvironment; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.Wizard; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IImportWizard; import org.eclipse.ui.IPerspectiveDescriptor; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.WorkbenchException; import org.eclipse.ui.actions.WorkspaceModifyOperation; import aQute.bnd.build.Project; import aQute.bnd.build.Workspace; import bndtools.BndConstants; import bndtools.Plugin; public class ImportBndWorkspaceWizard extends Wizard implements IImportWizard { private IWorkbench workbench; private ImportBndWorkspaceWizardPageOne mainPage; @Override public void init(IWorkbench workbench, IStructuredSelection selection) { this.workbench = workbench; setWindowTitle("Import Bnd Workspace"); setDefaultPageImageDescriptor(ImageDescriptor.createFromURL(Plugin.getDefault().getBundle().getEntry("icons/bndtools-wizban.png"))); } @Override public void addPages() { super.addPages(); mainPage = new ImportBndWorkspaceWizardPageOne("Select"); addPage(mainPage); } @Override public boolean performFinish() { final ImportSettings importSettings = new ImportSettings(mainPage.getSelectedFolder(), mainPage.isDeleteSettings(), mainPage.isInferExecutionEnvironment()); // create the new project operation final WorkspaceModifyOperation op = new WorkspaceModifyOperation() { @Override protected void execute(IProgressMonitor monitor) throws CoreException { try { importProjects(importSettings, monitor); } catch (Exception e) { throw new CoreException(new Status(IStatus.ERROR, Plugin.PLUGIN_ID, "Error during import of Bnd workspace!", e)); } } }; Job importJob = new Job("Import Bnd Workspace") { @Override protected IStatus run(IProgressMonitor monitor) { try { op.run(monitor); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof CoreException && ((CoreException) t).getStatus().getException() != null) { // unwrap the cause of the CoreException t = ((CoreException) t).getStatus().getException(); } return new Status(Status.ERROR, Plugin.PLUGIN_ID, "Could not finish import job for Bnd Workspace!", t); } catch (InterruptedException e) { return Status.CANCEL_STATUS; } return Status.OK_STATUS; } }; importJob.schedule(); try { // Prompt to switch to the BndTools perspective IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); IPerspectiveDescriptor currentPerspective = window.getActivePage().getPerspective(); if (!"bndtools.perspective".equals(currentPerspective.getId())) { if (MessageDialog.openQuestion(getShell(), "Bndtools Perspective", "Switch to the Bndtools perspective?")) { this.workbench.showPerspective("bndtools.perspective", window); } } } catch (WorkbenchException e) { error("Unable to switch to BndTools perspective", e); } return true; } private void deleteOldProjectFiles(final Path projectPath) throws IOException { final Path settings = projectPath.resolve(".settings"); if (Files.exists(settings)) { Files.walkFileTree(settings, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }); } final Path project = projectPath.resolve(".project"); Files.deleteIfExists(project); final Path classpath = projectPath.resolve(".classpath"); Files.deleteIfExists(classpath); } private boolean importProjects(final ImportSettings importSettings, IProgressMonitor monitor) throws Exception { Workspace bndWorkspace = Workspace.getWorkspace(importSettings.rootImportPath); int steps = bndWorkspace.getAllProjects().size() + 2; monitor.beginTask("Importing Bnd workspace", steps); importConfigurationProject(importSettings, monitor); // Import Projects for (Project bndProject : bndWorkspace.getAllProjects()) { importBndProject(bndProject, bndWorkspace, importSettings, monitor); } // build monitor.subTask("Building workspace"); ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.CLEAN_BUILD, monitor); monitor.worked(1); monitor.done(); return true; } private void importConfigurationProject(final ImportSettings importSettings, IProgressMonitor monitor) throws Exception { monitor.subTask("Import configuration project 'cnf'."); final IWorkspace eclipseWorkspace = ResourcesPlugin.getWorkspace(); final IWorkspaceRoot eclipseWorkspaceRoot = eclipseWorkspace.getRoot(); final Workspace bndWorkspace = Workspace.getWorkspace(importSettings.rootImportPath); // Prepare Eclipse Workspace updateEclipseWorkspaceSettings(bndWorkspace); // create generic project final IProjectDescription cnfProjectDescription = eclipseWorkspace.newProjectDescription(Workspace.CNFDIR); final IProject project = eclipseWorkspaceRoot.getProject(Workspace.CNFDIR); if (importSettings.deleteSettings) { deleteOldProjectFiles(Paths.get(bndWorkspace.getBase().toURI()).resolve(Workspace.CNFDIR)); } // create JavaProject IPath path = URIUtil.toPath(bndWorkspace.getBuildDir().toURI()); if (Platform.getLocation().isPrefixOf(path)) { cnfProjectDescription.setLocation(null); } else { cnfProjectDescription.setLocation(path); } if (!project.exists()) { project.create(cnfProjectDescription, monitor); } if (!project.isOpen()) { project.open(monitor); } monitor.worked(1); } private void importBndProject(final Project bndProject, final Workspace bndWorkspace, final ImportSettings importSettings, IProgressMonitor monitor) throws IOException, CoreException, Exception { monitor.subTask("Import Bnd project '" + bndProject.getName() + "'."); final IWorkspace eclipseWorkspace = ResourcesPlugin.getWorkspace(); final IWorkspaceRoot eclipseWorkspaceRoot = eclipseWorkspace.getRoot(); if (importSettings.deleteSettings) { deleteOldProjectFiles(Paths.get(bndWorkspace.getBaseURI()).resolve(bndProject.getName())); } // create generic project final IProjectDescription projectDescription = eclipseWorkspace.newProjectDescription(bndProject.getName()); final IProject project = eclipseWorkspaceRoot.getProject(bndProject.getName()); IPath path = URIUtil.toPath(bndProject.getBaseURI()); if (Platform.getLocation().isPrefixOf(path)) { projectDescription.setLocation(null); } else { projectDescription.setLocation(path); } if (!project.exists()) { project.create(projectDescription, monitor); } project.open(monitor); setNatures(project, monitor, JavaCore.NATURE_ID, Plugin.BNDTOOLS_NATURE); IJavaProject javaProject = JavaCore.create(project); if (!javaProject.isOpen()) { javaProject.open(monitor); } updateJavaProjectSettings(bndProject, javaProject, importSettings, monitor); importSourceAndOutputFolders(bndProject, project, javaProject, monitor); } private void importSourceAndOutputFolders(Project bndProject, IProject workspaceProject, IJavaProject javaProject, IProgressMonitor monitor) throws Exception { // remove defaults removeClasspathDefaults(javaProject); // Output IFolder sourceOutput = workspaceProject.getFolder(URIUtil.toPath(bndProject.getSrcOutput().toURI()).makeRelativeTo(workspaceProject.getLocation())); IPackageFragmentRoot outputRoot = javaProject.getPackageFragmentRoot(sourceOutput); // Source (multiple possible) for (File folder : bndProject.getSourcePath()) { IFolder source = workspaceProject.getFolder(URIUtil.toPath(folder.toURI()).makeRelativeTo(workspaceProject.getLocation())); // Now the created source folder should be added to the class entries of the project, otherwise compilation will fail IPackageFragmentRoot root = javaProject.getPackageFragmentRoot(source); List<IClasspathEntry> entries = new ArrayList<>(Arrays.asList(javaProject.getRawClasspath())); entries.add(JavaCore.newSourceEntry(root.getPath(), null, outputRoot.getPath())); javaProject.setRawClasspath(entries.toArray(new IClasspathEntry[entries.size()]), monitor); createFolderIfNecessary(source, monitor); } // Test-Source javaProject.setOutputLocation(sourceOutput.getFullPath(), null); if (!bndProject.getSrcOutput().equals(bndProject.getTestOutput())) { IFolder testOutput = workspaceProject.getFolder(URIUtil.toPath(bndProject.getTestOutput().toURI()).makeRelativeTo(workspaceProject.getLocation())); IPackageFragmentRoot testOutputRoot = javaProject.getPackageFragmentRoot(testOutput); IFolder testSource = workspaceProject.getFolder(URIUtil.toPath(bndProject.getTestSrc().toURI()).makeRelativeTo(workspaceProject.getLocation())); // Now the created source folder should be added to the class entries of the project, otherwise compilation will fail IPackageFragmentRoot root = javaProject.getPackageFragmentRoot(testSource); List<IClasspathEntry> entries = new ArrayList<>(Arrays.asList(javaProject.getRawClasspath())); entries.add(JavaCore.newSourceEntry(root.getPath(), null, testOutputRoot.getPath())); javaProject.setRawClasspath(entries.toArray(new IClasspathEntry[entries.size()]), monitor); createFolderIfNecessary(testSource, monitor); } // Generated Artifact IFolder generated = workspaceProject.getFolder(URIUtil.toPath(bndProject.getTarget().toURI()).makeRelativeTo(workspaceProject.getLocation())); createFolderIfNecessary(generated, monitor); } private void createFolderIfNecessary(IFolder folder, IProgressMonitor monitor) throws CoreException { if (!folder.exists()) { folder.create(true, true, monitor); } } /** * The Java-Nature doesn't add a JRE-Container, so we add one * * @param javaProject * the Java project which should get enhanced with LibraryContainer * @param javacTarget * @param monitor * current IProgressMonitor * @throws JavaModelException */ private void addSystemLibraryContainer(final IJavaProject javaProject, final String javacTarget, final ImportSettings importSettings, IProgressMonitor monitor) throws JavaModelException { List<IClasspathEntry> entries = new ArrayList<>(Arrays.asList(javaProject.getRawClasspath())); // entries.addAll(newContainerEntries); // only add JRE-Container if none available boolean jreContainerAvailable = false; Iterator<IClasspathEntry> it = entries.iterator(); while (it.hasNext()) { IClasspathEntry entry = it.next(); if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER && entry.getPath() != null && entry.getPath().toString().startsWith(JavaRuntime.JRE_CONTAINER)) { // remove existing entry if user want to infer EE if (importSettings.inferExecutionEnvironment) { it.remove(); } else { jreContainerAvailable = true; } break; } } if (!jreContainerAvailable) { IClasspathEntry defaultJREContainerEntry = JavaRuntime.getDefaultJREContainerEntry(); if (importSettings.inferExecutionEnvironment) { // fuzzy at the moment but better than nothing. We should find a way to handle CDC IExecutionEnvironment environment = JavaRuntime.getExecutionEnvironmentsManager().getEnvironment("J2SE-" + javacTarget); if (environment == null) { environment = JavaRuntime.getExecutionEnvironmentsManager().getEnvironment("JavaSE-" + javacTarget); } if (environment != null) { entries.add(JavaCore.newContainerEntry(JavaRuntime.newJREContainerPath(environment))); } else { Plugin.getDefault().getLog() .log(new Status(IStatus.WARNING, Plugin.PLUGIN_ID, 0, String.format("Could not infer execution-environment in project '%s' for javac.target '%s'", javaProject.getElementName(), javacTarget), null)); entries.add(defaultJREContainerEntry); } } else { entries.add(defaultJREContainerEntry); } javaProject.setRawClasspath(entries.toArray(new IClasspathEntry[entries.size()]), monitor); } } /** * Update Eclipse workspace with information from a Bnd workspace Currently only compiler-settings are matched * * @param bndWorkspace * the imported Bnd workspace */ private void updateEclipseWorkspaceSettings(final Workspace bndWorkspace) { final String javacSource = bndWorkspace.getProperties().getProperty(BndConstants.JAVAC_SOURCE); final String javacTarget = bndWorkspace.getProperties().getProperty(BndConstants.JAVAC_TARGET); @SuppressWarnings("unchecked") Hashtable<String,String> javaCoreOptions = JavaCore.getOptions(); if (javacSource != null) { javaCoreOptions.put(JavaCore.COMPILER_SOURCE, javacSource); } if (javacTarget != null) { javaCoreOptions.put(JavaCore.COMPILER_COMPLIANCE, javacTarget); javaCoreOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, javacTarget); } JavaCore.setOptions(javaCoreOptions); } /** * Updates the JavaProject with project-level settings from a Bnd project. Currently only compiler-settings are * matched if they differ from the Eclipse workspace (which has been set prior), as well as the JRE-SystemLibrary. * * @param bndProject * the imported BndProject * @param javaProject * the newly created JavaProject * @throws JavaModelException */ private void updateJavaProjectSettings(final Project bndProject, final IJavaProject javaProject, final ImportSettings importSettings, IProgressMonitor monitor) throws JavaModelException { final String javacSource = bndProject.getProperties().getProperty(BndConstants.JAVAC_SOURCE); final String javacTarget = bndProject.getProperties().getProperty(BndConstants.JAVAC_TARGET); addSystemLibraryContainer(javaProject, javacTarget, importSettings, monitor); @SuppressWarnings("unchecked") Map<String,String> projectOptions = javaProject.getOptions(false); // only update project-specific settings when different from workspace if (javacSource != null && !javacSource.equals(JavaCore.getOption(JavaCore.COMPILER_SOURCE))) { projectOptions.put(JavaCore.COMPILER_SOURCE, javacSource); } if (javacTarget != null && !javacTarget.equals(JavaCore.getOption(JavaCore.COMPILER_COMPLIANCE))) { projectOptions.put(JavaCore.COMPILER_COMPLIANCE, javacTarget); } if (javacTarget != null && !javacTarget.equals(JavaCore.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM))) { projectOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, javacTarget); } javaProject.setOptions(projectOptions); } /** * Creating a JavaProject has the effect that there is an default source-folder in the RawClasspath which uses the * project-directory itself. Furthermore, the BndProjectNature causes the classpath container to be available if the * Repositories-View is still populated from a prior Workspace-Setup. * * @param javaProject * the Project which needs special treatment * @throws JavaModelException */ private void removeClasspathDefaults(IJavaProject javaProject) throws JavaModelException { for (IPackageFragmentRoot root : javaProject.getPackageFragmentRoots()) { if (!root.isArchive()) { int jCoreFlags = IPackageFragmentRoot.NO_RESOURCE_MODIFICATION | IPackageFragmentRoot.ORIGINATING_PROJECT_CLASSPATH; root.delete(IResource.NONE, jCoreFlags, null); } } } private void setNatures(IProject project, IProgressMonitor monitor, String... natureIds) throws CoreException { IProjectDescription updatingDescription = project.getDescription(); updatingDescription.setNatureIds(natureIds); project.setDescription(updatingDescription, monitor); } private void error(final String message, final Throwable t) { // Log error Plugin.getDefault().getLog().log(new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, message, t)); // build the error message and include the current stack trace final MultiStatus status = createMultiStatus(t); Runnable run = new Runnable() { @Override public void run() { // show error dialog ErrorDialog.openError(null, "Error", message, status); } }; if (Display.getCurrent() == null) { Display.getDefault().asyncExec(run); } else { run.run(); } } /* * TODO probably something to move to Plugin * * creates a MultiStatus including StackTrace */ private static MultiStatus createMultiStatus(Throwable t) { List<Status> childStatuses = new ArrayList<>(); StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace(); for (StackTraceElement stackTrace : stackTraces) { Status status = new Status(IStatus.ERROR, Plugin.PLUGIN_ID, stackTrace.toString()); childStatuses.add(status); } MultiStatus ms = new MultiStatus(Plugin.PLUGIN_ID, IStatus.ERROR, childStatuses.toArray(new Status[] {}), t.toString(), t); return ms; } /** * Wrapper class to hand user-settings for the import down the call-stack */ private static final class ImportSettings { final File rootImportPath; final boolean deleteSettings; final boolean inferExecutionEnvironment; private ImportSettings(File rootImportPath, boolean deleteSettings, boolean inferExecutionEnvironment) { this.rootImportPath = rootImportPath; this.deleteSettings = deleteSettings; this.inferExecutionEnvironment = inferExecutionEnvironment; } } }