Java tutorial
/** * Copyright (c) 2016 NumberFour AG. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * NumberFour AG - Initial API and implementation */ package eu.numberfour.n4js.ui.export; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.DirectoryDialog; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.dialogs.IOverwriteQuery; import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; import org.eclipse.ui.internal.wizards.datatransfer.DataTransferMessages; import org.eclipse.ui.model.WorkbenchContentProvider; import org.eclipse.ui.model.WorkbenchLabelProvider; import com.google.common.base.Optional; import com.google.common.collect.Lists; import com.google.inject.Inject; import eu.numberfour.n4js.ui.projectModel.IN4JSEclipseCore; import eu.numberfour.n4js.ui.projectModel.IN4JSEclipseProject; /** * Mostly adapted from {@link org.eclipse.ui.internal.wizards.datatransfer.WizardArchiveFileResourceExportPage1} and its * supertypes. */ @SuppressWarnings("restriction") public abstract class AbstractExportToSingleFileWizardPage extends WizardPage implements Listener, IOverwriteQuery { private static final int SIZING_TEXT_FIELD_WIDTH = 250; private static final int COMBO_HISTORY_LENGTH = 5; private Combo destinationNameField; private Button destinationBrowseButton; private Button overwriteExistingFilesCheckbox; /** The initial selection, used to retrieve the main file and project */ protected final IStructuredSelection initialResourceSelection; /** The list viewer with the projects, created in {@link #createProjectList(Composite)} */ protected CheckboxTableViewer listViewer; @Inject private IWorkbench workbench; @Inject private IWorkspace workspace; @Inject private IN4JSEclipseCore n4jsCore; /** * Create an instance of this class. It may be configured with a selection of {@link IResource resources}. This * constraint is declared in the plugin.xml. * * @param selection * the selection which contains IResources */ public AbstractExportToSingleFileWizardPage(IStructuredSelection selection, String name, String title, String description) { super(name); this.initialResourceSelection = selection; setTitle(title); setDescription(description); } /** * ID for storing the content of the destination when the page was closed, in order to recreate the content the next * time the page is shown. */ protected abstract String getStoreDestinationNamesID(); /** * ID for storing the value of the overwrite flag when the page was closed, in order to recreate the content the * next time the page is shown. */ protected abstract String getStoreOverwriteExistingFilesID(); /** * The output suffix of the file to be exported to, e.g., ".nfar" or ".js". */ protected abstract String getOutputSuffix(); /** * Creates the operation actually performing the export, to be implemented by subclasses. */ protected abstract AbstractExportOperation createExportOperation(File archiveFile, IFile mainFile, IN4JSEclipseProject eclipseProject); /** * Adds an entry to a history, while taking care of duplicate history items and excessively long histories. The * assumption is made that all histories should be of length * <code>WizardDataTransferPage.COMBO_HISTORY_LENGTH</code>. * * @param history * the current history * @param newEntry * the entry to add to the history */ private String[] addToHistory(String[] history, String newEntry) { List<String> l = new ArrayList<>(Arrays.asList(history)); addToHistory(l, newEntry); String[] r = new String[l.size()]; l.toArray(r); return r; } /** * Adds an entry to a history, while taking care of duplicate history items and excessively long histories. The * assumption is made that all histories should be of length <code>COMBO_HISTORY_LENGTH</code>. * * @param history * the current history * @param newEntry * the entry to add to the history */ private void addToHistory(List<String> history, String newEntry) { history.remove(newEntry); history.add(0, newEntry); // since only one new item was added, we can be over the limit // by at most one item if (history.size() > COMBO_HISTORY_LENGTH) { history.remove(COMBO_HISTORY_LENGTH); } } /** * Returns whether this page is complete. This determination is made based upon the current contents of this page's * controls. * * @return <code>true</code> if this page is complete, and <code>false</code> if incomplete * @see #validateSourceGroup() * @see #validateDestinationGroup() */ private boolean determinePageCompletion() { boolean complete = validateSourceGroup() && validateDestinationGroup(); // Avoid draw flicker by not clearing the error // message unless all is valid. if (complete) { setErrorMessage(null); } return complete; } /** * The default implementation of this <code>IOverwriteQuery</code> method asks the user whether the existing * resource at the given path should be overwritten. * * @param pathString * the path of the archive * @return the user's reply: one of <code>"YES"</code>, <code>"NO"</code>, <code>"ALL"</code>, or * <code>"CANCEL"</code> */ @Override public String queryOverwrite(String pathString) { IPath path = Path.fromOSString(pathString); String messageString; // Break the message up if there is a file name and a directory // and there are at least 2 segments. if (path.getFileExtension() == null || path.segmentCount() < 2) { messageString = NLS.bind(IDEWorkbenchMessages.WizardDataTransfer_existsQuestion, pathString); } else { messageString = NLS.bind(IDEWorkbenchMessages.WizardDataTransfer_overwriteNameAndPathQuestion, path.lastSegment(), path.removeLastSegments(1).toOSString()); } final MessageDialog dialog = new MessageDialog(getContainer().getShell(), IDEWorkbenchMessages.Question, null, messageString, MessageDialog.QUESTION, new String[] { IDialogConstants.YES_LABEL, IDialogConstants.YES_TO_ALL_LABEL, IDialogConstants.NO_LABEL, IDialogConstants.NO_TO_ALL_LABEL, IDialogConstants.CANCEL_LABEL }, 0) { @Override protected int getShellStyle() { return super.getShellStyle() | SWT.SHEET; } }; String[] response = new String[] { YES, ALL, NO, NO_ALL, CANCEL }; // run in syncExec because callback is from an operation, // which is probably not running in the UI thread. getControl().getDisplay().syncExec(new Runnable() { @Override public void run() { dialog.open(); } }); return dialog.getReturnCode() < 0 ? CANCEL : response[dialog.getReturnCode()]; } /** * Displays a Yes/No question to the user with the specified message and returns the user's response. * * @param message * the question to ask * @return <code>true</code> for Yes, and <code>false</code> for No */ private boolean queryYesNoQuestion(String message) { MessageDialog dialog = new MessageDialog(getContainer().getShell(), IDEWorkbenchMessages.Question, (Image) null, message, MessageDialog.NONE, new String[] { IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL }, 0) { @Override protected int getShellStyle() { return super.getShellStyle() | SWT.SHEET; } }; return dialog.open() == 0; } /** * Restores control settings that were saved in the previous instance of this page. */ private void restoreWidgetValues() { IDialogSettings settings = getDialogSettings(); if (settings != null) { String[] directoryNames = settings.getArray(getStoreDestinationNamesID()); if (directoryNames == null) { return; // ie.- no settings stored } // destination setDestinationValue(directoryNames[0]); for (int i = 0; i < directoryNames.length; i++) { addDestinationItem(directoryNames[i]); } // options overwriteExistingFilesCheckbox.setSelection(settings.getBoolean(getStoreOverwriteExistingFilesID())); } } /** * Hook method for saving widget values for restoration by the next instance of this class. */ private void saveWidgetValues() { // update directory names history IDialogSettings settings = getDialogSettings(); if (settings != null) { String[] directoryNames = settings.getArray(getStoreDestinationNamesID()); if (directoryNames == null) { directoryNames = new String[0]; } directoryNames = addToHistory(directoryNames, getTargetDirectory()); settings.put(getStoreDestinationNamesID(), directoryNames); // options settings.put(getStoreOverwriteExistingFilesID(), overwriteExistingFilesCheckbox.getSelection()); } } /** * Determine if the page is complete and update the page appropriately. */ private void updatePageCompletion() { boolean pageComplete = determinePageCompletion(); setPageComplete(pageComplete); if (pageComplete) { setErrorMessage(null); } } /** * Updates the enable state of this page's controls. */ private void updateWidgetEnablements() { boolean pageComplete = determinePageCompletion(); setPageComplete(pageComplete); if (pageComplete) { setMessage(null); } } /** * Answer a boolean indicating whether the receivers destination specification widgets currently all contain valid * values. */ private boolean validateDestinationGroup() { String destinationValue = getTargetDirectory(); if (destinationValue.length() == 0) { setMessage(destinationEmptyMessage()); return false; } String conflictingContainer = getConflictingContainerNameFor(destinationValue); if (conflictingContainer == null) { // // no error message, but warning may exists // String threatenedContainer = getOverlappingProjectName(destinationValue); // if (threatenedContainer == null) setMessage(null); // else // setMessage(NLS.bind(NFARExportMessages.FileExport_damageWarning, threatenedContainer), WARNING); } else { setErrorMessage(NLS.bind(N4ExportMessages.FileExport_conflictingContainer, conflictingContainer)); giveFocusToDestination(); return false; } return true; } /** * Returns whether this page's source specification controls currently all contain valid values. * * @return <code>true</code> to indicate validity of all controls in the source specification group */ protected boolean validateSourceGroup() { // there must be some resources selected for Export boolean isValid = listViewer.getCheckedElements().length == 1; if (!isValid) { setErrorMessage(N4ExportMessages.FileExport_noneSelected); isValid = false; } else { setErrorMessage(null); } return isValid; } /** * Create the options specification widgets. */ protected void createOptionsGroup(Composite parent) { // options group Group optionsGroup = new Group(parent, SWT.NONE); GridLayout layout = new GridLayout(); optionsGroup.setLayout(layout); optionsGroup.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); optionsGroup.setText(IDEWorkbenchMessages.WizardExportPage_options); optionsGroup.setFont(parent.getFont()); createOptionsGroupButtons(optionsGroup); } /** * Display an error dialog with the specified message. */ private void displayErrorDialog(String message) { MessageDialog.open(MessageDialog.ERROR, getContainer().getShell(), getErrorDialogTitle(), message, SWT.SHEET); } /** * Display an error dialog with the information from the supplied exception. */ private void displayErrorDialog(Throwable exception) { String message = exception.getMessage(); // Some system exceptions have no message if (message == null) { message = NLS.bind(IDEWorkbenchMessages.WizardDataTransfer_exceptionMessage, exception); } displayErrorDialog(message); } /** * Get the title for an error dialog. */ private String getErrorDialogTitle() { return IDEWorkbenchMessages.WizardExportPage_errorDialogTitle; } /** * Add the passed value to self's destination widget's history */ private void addDestinationItem(String value) { destinationNameField.add(value); } /** * Create the export destination specification widgets */ protected void createDestinationGroup(Composite parent) { Font font = parent.getFont(); // destination specification group Composite destinationSelectionGroup = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(); layout.numColumns = 3; destinationSelectionGroup.setLayout(layout); destinationSelectionGroup .setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_FILL)); destinationSelectionGroup.setFont(font); Label destinationLabel = new Label(destinationSelectionGroup, SWT.NONE); destinationLabel.setText(getTargetLabel()); destinationLabel.setFont(font); // destination name entry field destinationNameField = new Combo(destinationSelectionGroup, SWT.SINGLE | SWT.BORDER); destinationNameField.addListener(SWT.Modify, this); destinationNameField.addListener(SWT.Selection, this); GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL); data.widthHint = SIZING_TEXT_FIELD_WIDTH; destinationNameField.setLayoutData(data); destinationNameField.setFont(font); // destination browse button destinationBrowseButton = new Button(destinationSelectionGroup, SWT.PUSH); destinationBrowseButton.setText(N4ExportMessages.DataTransfer_browse); destinationBrowseButton.addListener(SWT.Selection, this); destinationBrowseButton.setFont(font); setButtonLayoutData(destinationBrowseButton); // new Label(parent, SWT.NONE); // vertical spacer } /** * Creates the export options group controls. */ private void createOptionsGroupButtons(Group optionsGroup) { Font font = optionsGroup.getFont(); createOverwriteExisting(optionsGroup, font); } /** * Create the button for checking if we should ask if we are going to overwrite existing files. */ private void createOverwriteExisting(Group optionsGroup, Font font) { // overwrite... checkbox overwriteExistingFilesCheckbox = new Button(optionsGroup, SWT.CHECK | SWT.LEFT); overwriteExistingFilesCheckbox.setText(N4ExportMessages.ExportFile_overwriteExisting); overwriteExistingFilesCheckbox.setFont(font); } /** * Attempts to ensure that the specified directory exists on the local file system. Answers a boolean indicating * success. */ private boolean ensureDirectoryExists(File directory) { if (!directory.exists()) { if (!queryYesNoQuestion(N4ExportMessages.DataTransfer_createTargetDirectory)) { return false; } if (!directory.mkdirs()) { displayErrorDialog(N4ExportMessages.DataTransfer_directoryCreationError); giveFocusToDestination(); return false; } } return true; } /** * If the target for export does not exist then attempt to create it. Answer a boolean indicating whether the target * exists (ie.- if it either pre-existed or this method was able to create it) */ private boolean ensureTargetDirectoryIsValid(File targetDirectory) { if (targetDirectory.exists() && !targetDirectory.isDirectory()) { displayErrorDialog(DataTransferMessages.FileExport_directoryExists); giveFocusToDestination(); return false; } return ensureDirectoryExists(targetDirectory); } /** * The Finish button was pressed. Try to do the required work now and answer a boolean indicating success. If false * is returned then the wizard will not close. */ public boolean finish() { IProject selectedProject = (IProject) listViewer.getCheckedElements()[0]; if (!ensureTargetIsValid()) { return false; } // Save dirty editors if possible but do not stop if not all are saved saveDirtyEditors(); // about to invoke the operation so save our state saveWidgetValues(); Optional<? extends IN4JSEclipseProject> eclipseProjectOpt = n4jsCore.create(selectedProject); if (eclipseProjectOpt.isPresent()) { String targetDirectory = getTargetDirectory(); File archiveFile = new File(targetDirectory, getTargetFileName()); // only certain Exporters demand the first selected element to be a 'file', which will be denoted as // main-file or the thing to execute (ObjectiveCwrapper), // others don't care. If the export is triggered on a project the first element is not an IFile ! IFile mainFile = null; Object firstElement = initialResourceSelection.getFirstElement(); if (firstElement instanceof IFile) mainFile = (IFile) firstElement; try { boolean result = executeExportOperation( createExportOperation(archiveFile, mainFile, eclipseProjectOpt.get())); return result; } finally { String overlappingProject = getOverlappingProjectName(targetDirectory); try { workspace.getRoot().getProject(overlappingProject).refreshLocal(IResource.DEPTH_INFINITE, null); } catch (CoreException e) { // ignore } } } return true; } /** * Answer the string to display in the receiver as the destination type */ private String getTargetLabel() { return N4ExportMessages.FileExport_toDirectory; } /** * Answer the contents of self's destination specification widget. */ private String getTargetDirectory() { String destinationText = destinationNameField.getText().trim(); return destinationText; } /** * Set the current input focus to self's destination entry field */ private void giveFocusToDestination() { destinationNameField.setFocus(); } /** * Open an appropriate destination browser so that the user can specify a source to import from */ private void handleDestinationBrowseButtonPressed() { DirectoryDialog dialog = new DirectoryDialog(getContainer().getShell(), SWT.SAVE | SWT.SHEET); dialog.setMessage(N4ExportMessages.FileExport_selectDestinationMessage); dialog.setText(N4ExportMessages.FileExport_selectDestinationTitle); dialog.setFilterPath(getTargetDirectory()); String selectedDirectoryName = dialog.open(); if (selectedDirectoryName != null) { setErrorMessage(null); setDestinationValue(selectedDirectoryName); } } /** * Handle all events and enablements for widgets in this page * * @param e * Event */ @Override public void handleEvent(Event e) { Widget source = e.widget; if (source == destinationBrowseButton) { handleDestinationBrowseButtonPressed(); } updatePageCompletion(); } /** * Set the contents of the receivers destination specification widget to the passed value * */ private void setDestinationValue(String value) { destinationNameField.setText(value); } /** * Get the message used to denote an empty destination. */ private String destinationEmptyMessage() { return N4ExportMessages.ArchiveExport_destinationEmpty; } /** * Returns the name of a container with a location that encompasses targetDirectory. Returns null if there is no * conflict. * * @param targetDirectory * the path of the directory to check. * @return the conflicting container name or <code>null</code> */ private String getConflictingContainerNameFor(String targetDirectory) { IPath rootPath = ResourcesPlugin.getWorkspace().getRoot().getLocation(); IPath testPath = new Path(targetDirectory); // cannot export into workspace root if (testPath.equals(rootPath)) return rootPath.lastSegment(); // Are they the same? if (testPath.matchingFirstSegments(rootPath) == rootPath.segmentCount()) { String firstSegment = testPath.removeFirstSegments(rootPath.segmentCount()).segment(0); if (!Character.isLetterOrDigit(firstSegment.charAt(0))) return firstSegment; } return null; } /** * Returns the name of a {@link IProject} with a location that includes targetDirectory. Returns null if there is no * such {@link IProject}. * * @param targetDirectory * the path of the directory to check. * @return the overlapping project name or <code>null</code> */ private String getOverlappingProjectName(String targetDirectory) { IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); IPath testPath = new Path(targetDirectory); IContainer[] containers = root.findContainersForLocationURI(testPath.makeAbsolute().toFile().toURI()); if (containers.length > 0) { return containers[0].getProject().getName(); } return null; } private void setupBasedOnInitialSelections() { listViewer.setSelection(initialResourceSelection); if (initialResourceSelection != null && !initialResourceSelection.isEmpty()) { Object first = initialResourceSelection.getFirstElement(); if (first instanceof IResource) { listViewer.setChecked(((IResource) first).getProject(), true); Object[] checkedElements = listViewer.getCheckedElements(); if (checkedElements.length == 1) { IProject selectedProject = (IProject) checkedElements[0]; setDestinationValue(selectedProject.getLocation().append("lib").toOSString()); } } } } /** * (non-Javadoc) Method declared on IDialogPage. */ @Override public void createControl(Composite parent) { initializeDialogUnits(parent); Composite composite = new Composite(parent, SWT.NULL); composite.setLayout(new GridLayout()); composite.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL)); composite.setFont(parent.getFont()); createExportGoups(composite); restoreWidgetValues(); // ie.- subclass hook if (initialResourceSelection != null) { setupBasedOnInitialSelections(); } updateWidgetEnablements(); setPageComplete(determinePageCompletion()); setErrorMessage(null); // should not initially have error message setControl(composite); giveFocusToDestination(); } /** * Create export groups with projecdt list, destination field and option groups. May be overridden by sub classes if * other groups are to be shown. * * @param composite * the parent of the groups */ protected void createExportGoups(Composite composite) { createProjectList(composite); createDestinationGroup(composite); createOptionsGroup(composite); } /** * Returns a boolean indicating whether the directory portion of the passed pathname is valid and available for use. */ protected boolean ensureTargetDirectoryIsValid(String fullPathname) { return ensureTargetDirectoryIsValid(new File(fullPathname)); } /** * Returns a boolean indicating whether the passed File handle is is valid and available for use. */ protected boolean ensureTargetFileIsValid(File targetFile) { if (targetFile.exists() && targetFile.isDirectory()) { displayErrorDialog(N4ExportMessages.Export_mustBeFile); giveFocusToDestination(); return false; } if (targetFile.exists()) { if (targetFile.canWrite()) { if (!this.overwriteExistingFilesCheckbox.getSelection() && !queryYesNoQuestion(N4ExportMessages.Export_alreadyExists)) { return false; } } else { displayErrorDialog(N4ExportMessages.Export_alreadyExistsError); giveFocusToDestination(); return false; } } return true; } /** * Ensures that the target output file and its containing directory are both valid and able to be used. Answer a * boolean indicating validity. */ private boolean ensureTargetIsValid() { String targetDir = getTargetDirectory(); // check the selected path itself if (!ensureTargetDirectoryIsValid(targetDir)) { return false; } // check the synthesized target file name String fileName = getTargetFileName(); if (!ensureTargetFileIsValid(new File(targetDir, fileName))) { return false; } return true; } /** * Returns the target file name computed from the input of the page. May be overridden by subclasses if target file * name is to be calculated differently. */ protected String getTargetFileName() { IProject selectedProject = (IProject) listViewer.getCheckedElements()[0]; return selectedProject.getName() + getOutputSuffix(); } /** * Export the passed resource and recursively export all of its child resources (iff it's a container). Answer a * boolean indicating success. */ private boolean executeExportOperation(AbstractExportOperation op) { try { getContainer().run(true, true, op); } catch (InterruptedException e) { return false; } catch (InvocationTargetException e) { displayErrorDialog(e.getTargetException()); return false; } IStatus status = op.getStatus(); if (!status.isOK()) { ErrorDialog.openError(getContainer().getShell(), N4ExportMessages.DataTransfer_exportProblems, null, // no special message status); return false; } return true; } /** * Creates the checkbox tree and list for selecting resources. * * @param parent * the parent control */ protected void createProjectList(Composite parent) { // create the input element, which has the root resource // as its only child List<IProject> input = new ArrayList<>(); IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); for (int i = 0; i < projects.length; i++) { if (projects[i].isOpen()) { input.add(projects[i]); } } listViewer = CheckboxTableViewer.newCheckList(parent, SWT.TOP | SWT.BORDER); GridData data = new GridData(GridData.FILL_BOTH); listViewer.getTable().setLayoutData(data); listViewer.setLabelProvider(WorkbenchLabelProvider.getDecoratingWorkbenchLabelProvider()); listViewer.setContentProvider(getContentProvider()); listViewer.setComparator(new ViewerComparator()); // check for initial modification to avoid work if no changes are made listViewer.addCheckStateListener(new ICheckStateListener() { @Override public void checkStateChanged(CheckStateChangedEvent event) { if (event.getChecked()) { for (Object currentlyChecked : listViewer.getCheckedElements()) { if (currentlyChecked != event.getElement()) { listViewer.setChecked(currentlyChecked, false); } } } updateWidgetEnablements(); } }); listViewer.setInput(workspace); } /** * Returns a content provider for the list dialog. It will return all projects in the workspace except the given * project, plus any projects referenced by the given project which do no exist in the workspace. * * @return the content provider that shows the project content */ private IStructuredContentProvider getContentProvider() { return new WorkbenchContentProvider() { @Override public Object[] getChildren(Object o) { if (!(o instanceof IWorkspace)) { return new Object[0]; } // Collect all the projects in the workspace except the given project IProject[] projects = ((IWorkspace) o).getRoot().getProjects(); List<IProject> applicableProjects = Lists.newArrayList(); Optional<? extends IN4JSEclipseProject> projectOpt = null; for (IProject candidate : projects) { projectOpt = n4jsCore.create(candidate); if (projectOpt.isPresent() && projectOpt.get().exists()) { applicableProjects.add(candidate); } } return applicableProjects.toArray(new IProject[applicableProjects.size()]); } }; } /** * Save any editors that the user wants to save before export. * * @return boolean if the save was successful. */ private boolean saveDirtyEditors() { return workbench.saveAllEditors(true); } }