package org.bndtools.core.ui.wizards.index; import java.io.File; import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.regex.PatternSyntaxException; import org.bndtools.core.ui.icons.Icons; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.jface.fieldassist.FieldDecorationRegistry; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.StyledCellLabelProvider; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.jface.window.Window; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; 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.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.DirectoryDialog; import org.eclipse.swt.widgets.Display; 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.Table; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.dialogs.ContainerSelectionDialog; import bndtools.Plugin; public class IndexerWizardPage extends WizardPage { private File baseDir; private String resourcePattern = "**.jar"; //$NON-NLS-1$ private List<Path> inputPaths = Collections.emptyList(); private String outputFileName = "index.xml.gz"; //$NON-NLS-1$ private IndexFormatStyle outputStyle = IndexFormatStyle.PRETTY_UNCOMPRESSED; private Text txtBaseDir; private Text txtResourcePattern; private Text txtOutputPrefix; private Button btnOutputCompressed; private Button btnOutputPretty; private Label lblOutputName; private SearchFilesJob updateInputFilesJob; private TableViewer vwrInputs; private Label lblInputCount; private final Image imgFile = Icons.desc("file").createImage(); //$NON-NLS-1$ private final Image imgWarning = Icons.desc("warning").createImage(); //$NON-NLS-1$ private final Image imgError = Icons.desc("error").createImage(); //$NON-NLS-1$ public IndexerWizardPage() { super("index"); //$NON-NLS-1$ setTitle(Messages.IndexerWizardPage_title); setDescription(Messages.IndexerWizardPage_description); } @Override public void createControl(Composite parent) { Composite composite = new Composite(parent, SWT.NULL); setControl(composite); // Create controls new Label(composite, SWT.NONE).setText(Messages.IndexerWizardPage_baseDir); txtBaseDir = new Text(composite, SWT.BORDER); newSpacer(composite); Composite cmpButtonBar = new Composite(composite, SWT.NONE); Button btnBrowseWorkspace = new Button(cmpButtonBar, SWT.PUSH); btnBrowseWorkspace.setText(Messages.IndexerWizardPage_browse); Button btnBrowseExternal = new Button(cmpButtonBar, SWT.PUSH); btnBrowseExternal.setText(Messages.IndexerWizardPage_browseExternal); new Label(composite, SWT.NONE).setText(Messages.IndexerWizardPage_resourcePattern); txtResourcePattern = new Text(composite, SWT.BORDER); ControlDecoration decorResourcePattern = new ControlDecoration(txtResourcePattern, SWT.LEFT | SWT.TOP, composite); decorResourcePattern.setImage(FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION).getImage()); decorResourcePattern.setMarginWidth(3); decorResourcePattern.setDescriptionText(Messages.IndexerWizardPage_resourcePatternHelp); decorResourcePattern.setShowHover(true); decorResourcePattern.setShowOnlyOnFocus(false); Label lblInputs = new Label(composite, SWT.NONE); lblInputs.setText(Messages.IndexerWizardPage_inputs); Table tblInputs = new Table(composite, SWT.MULTI | SWT.BORDER); vwrInputs = new TableViewer(tblInputs); vwrInputs.setContentProvider(ArrayContentProvider.getInstance()); vwrInputs.setLabelProvider(new StyledCellLabelProvider() { @Override public void update(ViewerCell cell) { Object elem = cell.getElement(); if (elem instanceof Path) { cell.setText(elem.toString()); cell.setImage(imgFile); } else if (elem instanceof IStatus) { IStatus status = (IStatus) elem; cell.setText(status.getMessage()); if (status.getSeverity() == IStatus.ERROR) cell.setImage(imgError); else if (status.getSeverity() == IStatus.WARNING) cell.setImage(imgWarning); } } }); newSpacer(composite); lblInputCount = new Label(composite, SWT.NONE); Label lblOutputs = new Label(composite, SWT.NONE); lblOutputs.setText(Messages.IndexerWizardPage_output); Group grpOutput = new Group(composite, SWT.NONE); new Label(grpOutput, SWT.NONE).setText(Messages.IndexerWizardPage_prefix); txtOutputPrefix = new Text(grpOutput, SWT.BORDER); newSpacer(grpOutput); btnOutputPretty = new Button(grpOutput, SWT.RADIO); btnOutputPretty.setText(Messages.IndexerWizardPage_prettyPrint); newSpacer(grpOutput); btnOutputCompressed = new Button(grpOutput, SWT.RADIO); btnOutputCompressed.setText(Messages.IndexerWizardPage_compressed); newSpacer(grpOutput); lblOutputName = new Label(grpOutput, SWT.NONE); // LAYOUT GridLayout gl; GridData gd; gl = new GridLayout(2, false); gl.horizontalSpacing = 10; composite.setLayout(gl); txtBaseDir.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); cmpButtonBar.setLayoutData(new GridData(SWT.RIGHT, SWT.FILL, false, false)); gl = new GridLayout(2, false); gl.marginHeight = 0; gl.marginWidth = 0; cmpButtonBar.setLayout(gl); btnBrowseWorkspace.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); btnBrowseExternal.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); txtResourcePattern.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); lblInputs.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); gd = new GridData(SWT.FILL, SWT.FILL, true, false); gd.heightHint = 120; gd.widthHint = 100; tblInputs.setLayoutData(gd); lblInputCount.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false)); lblOutputs.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); grpOutput.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); grpOutput.setLayout(new GridLayout(2, false)); txtOutputPrefix.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); btnOutputCompressed.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); btnOutputPretty.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); lblOutputName.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); // LOAD DATA if (baseDir != null) txtBaseDir.setText(baseDir.getAbsolutePath()); if (resourcePattern != null) txtResourcePattern.setText(resourcePattern); txtOutputPrefix.setText("index"); //$NON-NLS-1$ btnOutputCompressed.setSelection(outputStyle == IndexFormatStyle.COMPRESSED); btnOutputPretty.setSelection(outputStyle == IndexFormatStyle.PRETTY_UNCOMPRESSED); updateOutputFileName(); updateInputs(); // LISTENERS txtBaseDir.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent me) { String baseDirStr = txtBaseDir.getText(); baseDir = baseDirStr.isEmpty() ? null : new File(baseDirStr); validate(); updateInputs(); } }); txtResourcePattern.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent me) { resourcePattern = txtResourcePattern.getText(); updateInputs(); } }); btnBrowseWorkspace.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); ContainerSelectionDialog containerDlg = new ContainerSelectionDialog(getShell(), root, false, Messages.IndexerWizardPage_selectBaseDir); containerDlg.showClosedProjects(false); if (containerDlg.open() == Window.OK) { Object[] selection = containerDlg.getResult(); if (selection != null && selection.length > 0) { IPath workspacePath = (IPath) selection[0]; IResource resource = root.findMember(workspacePath); if (resource == null) MessageDialog.openError(getShell(), "Error", "Could not find resource for path " + workspacePath); else txtBaseDir.setText(resource.getLocation().toString()); } } } }); btnBrowseExternal.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { DirectoryDialog dirDialog = new DirectoryDialog(getShell()); dirDialog.setMessage(Messages.IndexerWizardPage_selectBaseDir); String selectedPath = dirDialog.open(); if (selectedPath != null) txtBaseDir.setText(selectedPath); } }); txtOutputPrefix.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent me) { updateOutputFileName(); validate(); } }); Listener outputRadioListener = new Listener() { @Override public void handleEvent(Event ev) { outputStyle = btnOutputCompressed.getSelection() ? IndexFormatStyle.COMPRESSED : IndexFormatStyle.PRETTY_UNCOMPRESSED; updateOutputFileName(); validate(); } }; btnOutputCompressed.addListener(SWT.Selection, outputRadioListener); btnOutputPretty.addListener(SWT.Selection, outputRadioListener); validate(); } private Label newSpacer(Composite composite) { // Wrapping this in a method makes it easier to ignore the unused warning return new Label(composite, SWT.NONE); // spacer } private void updateInputs() { if (updateInputFilesJob != null) { updateInputFilesJob.cancel(); } inputPaths = Collections.emptyList(); setPageComplete(false); vwrInputs.setInput(inputPaths); lblInputCount.setText(String.format("%d resources found", inputPaths.size())); lblInputCount.getParent().layout(new Control[] { lblInputCount }); final String resourcePattern = this.resourcePattern; final Display display = getShell().getDisplay(); if (baseDir == null) return; updateInputFilesJob = new SearchFilesJob(baseDir, resourcePattern); updateInputFilesJob.addJobChangeListener(new JobChangeAdapter() { @Override public void done(IJobChangeEvent event) { final IStatus status = updateInputFilesJob.getSearchResult(); display.syncExec(new Runnable() { @Override public void run() { inputPaths = updateInputFilesJob.getPaths(); if (inputPaths == null) inputPaths = Collections.emptyList(); if (status.isOK()) vwrInputs.setInput(updateInputFilesJob.getPaths()); else // The error/warning status is displayed in the table instead of the path list vwrInputs.setInput(Collections.singleton(status)); lblInputCount.setText(String.format("%d resources found", inputPaths.size())); lblInputCount.getParent().layout(new Control[] { lblInputCount }); validate(); } }); } }); updateInputFilesJob.setSystem(true); updateInputFilesJob.schedule(500); vwrInputs.setInput(Collections.singleton(new Status(IStatus.INFO, Plugin.PLUGIN_ID, 0, Messages.IndexerWizardPage_checking, null))); lblInputCount.setText("..."); lblInputCount.getParent().layout(new Control[] { lblInputCount }); } private void updateOutputFileName() { String prefix = txtOutputPrefix.getText(); String suffix = outputStyle == IndexFormatStyle.COMPRESSED ? "xml.gz" : "xml"; //$NON-NLS-1$ //$NON-NLS-2$ StringBuilder sb = new StringBuilder().append(prefix); if (sb.charAt(sb.length() - 1) != '.') sb.append('.'); sb.append(suffix); outputFileName = sb.toString(); lblOutputName.setText(String.format(Messages.IndexerWizardPage_outputFileMessage, outputFileName)); } private void validate() { String warning = null; if (baseDir == null) { setErrorMessage(null); setPageComplete(false); return; } if (!baseDir.exists()) { setErrorMessage(Messages.IndexerWizardPage_error_noSuchDir + baseDir.getAbsolutePath()); setPageComplete(false); return; } if (!baseDir.isDirectory()) { setErrorMessage(Messages.IndexerWizardPage_error_notDir + baseDir.getAbsolutePath()); setPageComplete(false); return; } try { FileSystem fs = FileSystems.getDefault(); fs.getPathMatcher("glob:" + resourcePattern); //$NON-NLS-1$ } catch (Exception e) { setErrorMessage(e.getMessage()); setPageComplete(false); return; } if (outputFileName.indexOf('/') > -1) { setErrorMessage(Messages.IndexerWizardPage_error_noSlashes); setPageComplete(false); } File outputFile = new File(baseDir, outputFileName); if (outputFile.exists()) { if (outputFile.isFile()) warning = Messages.IndexerWizardPage_warn_fileOverwrite + outputFile.getAbsolutePath(); else { setErrorMessage(Messages.IndexerWizardPage_error_fileExists + outputFile.getAbsolutePath()); setPageComplete(false); } } setErrorMessage(null); setMessage(warning, WARNING); setPageComplete(inputPaths != null && !inputPaths.isEmpty()); } public void setBaseDir(File baseDir) { this.baseDir = baseDir; if (isControlCreated() && !getControl().isDisposed()) txtBaseDir.setText(baseDir.getAbsolutePath()); } public File getBaseDir() { return baseDir; } public void setResourcePattern(String pattern) { this.resourcePattern = pattern; if (isControlCreated() && !getControl().isDisposed()) txtResourcePattern.setText(resourcePattern); } public String getResourcePattern() { return resourcePattern; } public IndexFormatStyle getOutputStyle() { return outputStyle; } public File getOutputFile() { return new File(baseDir, outputFileName); } public List<Path> getInputPaths() { try { if (updateInputFilesJob != null) updateInputFilesJob.join(); return inputPaths; } catch (InterruptedException e) { return Collections.emptyList(); } } @Override public void dispose() { super.dispose(); imgFile.dispose(); imgError.dispose(); imgWarning.dispose(); } private static class SearchFilesJob extends Job { private final File baseDir; private final String resourcePattern; private IStatus searchResult = Status.OK_STATUS; private List<Path> paths = Collections.emptyList(); private SearchFilesJob(File baseDir, String resourcePattern) { super(Messages.IndexerWizardPage_updateInputs); if (baseDir == null) throw new NullPointerException("baseDir cannot be null"); //$NON-NLS-1$ this.baseDir = baseDir; this.resourcePattern = resourcePattern; setSystem(true); setUser(false); } @Override protected IStatus run(IProgressMonitor monitor) { try { final List<Path> result = new LinkedList<>(); // Collect the files final Path basePath = baseDir.toPath(); FileSystem fs = FileSystems.getDefault(); final PathMatcher matcher = fs.getPathMatcher("glob:" + resourcePattern); //$NON-NLS-1$ Files.walkFileTree(basePath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { Path relative = basePath.relativize(path); if (matcher.matches(relative)) result.add(relative); return FileVisitResult.CONTINUE; } }); if (result.isEmpty()) searchResult = new Status(IStatus.WARNING, Plugin.PLUGIN_ID, 0, Messages.IndexerWizardPage_warn_noMatchingFiles, null); else searchResult = Status.OK_STATUS; paths = result; } catch (PatternSyntaxException e) { searchResult = new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, Messages.IndexerWizardPage_error_invalidPattern + e.getMessage(), e); } catch (Exception e) { searchResult = new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, Messages.IndexerWizardPage_error_fileSearch, e); } return Status.OK_STATUS; } public IStatus getSearchResult() { return searchResult; } public List<Path> getPaths() { return paths; } } }