package bndtools.wizards.workspace; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.bndtools.templating.Resource; import org.bndtools.templating.ResourceMap; import org.bndtools.templating.Template; import org.bndtools.utils.jface.ItalicStyler; import org.bndtools.utils.swt.SWTConcurrencyUtil; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StyledCellLabelProvider; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.jface.wizard.IWizardContainer; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; 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.Label; import org.eclipse.swt.widgets.Table; import org.eclipse.ui.plugin.AbstractUIPlugin; import aQute.lib.io.IO; import bndtools.Plugin; import bndtools.utils.ModificationLock; public class WorkspacePreviewPage extends WizardPage { private final Object lock = new Object(); private final ModificationLock modifyLock = new ModificationLock(); private final Set<String> existingFiles = new HashSet<>(); private final Map<String,String> resourceErrors = new HashMap<>(); private final Set<String> checkedPaths = new HashSet<>(); private static final String MSG_NOTHING_SELECTED = "Select an entry to view details"; private File targetDir; private Template template; private ResourceMap templateOutputs = null; private String errorMessage = null; private boolean seen = false; private Table tblOutputs; private CheckboxTableViewer vwrOutputs; private Image imgAdded; private Image imgOverwrite; private Image imgError; private final IRunnableWithProgress calculatePreviewTask = new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { synchronized (lock) { try { if (templateOutputs == null) { errorMessage = null; existingFiles.clear(); resourceErrors.clear(); templateOutputs = template.generateOutputs(Collections.<String, List<Object>> emptyMap(), monitor); IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); for (Entry<String,Resource> entry : templateOutputs.entries()) { // Check for existing files File file = new File(targetDir, entry.getKey()); switch (entry.getValue().getType()) { case Folder : if (file.exists() && !file.isDirectory()) resourceErrors.put(entry.getKey(), String.format("Path already exists and is not a directory: %s", entry.getKey())); break; case File : if (file.exists() && !isEqualContent(file, entry.getValue().getContent())) { existingFiles.add(entry.getKey()); if (!file.isFile()) resourceErrors.put(entry.getKey(), String.format("Path already exists and is not a plain file: %s", entry.getKey())); } break; default : // ignore } // If the base folder has the same name as a project, but the project has a different location in the filesystem, then we // can't import it. String projectFolderName; int slashIndex = entry.getKey().indexOf('/'); if (slashIndex < 0) projectFolderName = entry.getKey(); else projectFolderName = entry.getKey().substring(0, slashIndex); File projectFolder = new File(targetDir, projectFolderName); IProject project = workspaceRoot.getProject(projectFolderName); if (project.exists()) { File existingProjectLoc = project.getLocation().toFile(); if (!existingProjectLoc.equals(projectFolder)) resourceErrors.put(entry.getKey(), String.format("Cannot import project directory %s as it clashes with an existing project '%s' at location %s", projectFolder.getAbsolutePath(), project.getName(), existingProjectLoc.getAbsolutePath())); } } } } catch (Exception e) { errorMessage = e.getMessage(); } } SWTConcurrencyUtil.execForControl(tblOutputs, true, updateDisplayTask); } }; private final Runnable updateDisplayTask = new Runnable() { @Override public void run() { synchronized (lock) { if (errorMessage != null) { setErrorMessage(errorMessage); } else if (!resourceErrors.isEmpty()) { setErrorMessage("Cannot expand the template due to errors on some resources."); } else { setErrorMessage(null); if (!existingFiles.isEmpty()) { setMessage("Some files will be overwritten", WARNING); } else { setMessage(null, WARNING); } } List<String> viewerInput; if (templateOutputs != null) { viewerInput = new ArrayList<>(templateOutputs.entries().size()); for (Entry<String,Resource> entry : templateOutputs.entries()) viewerInput.add(entry.getKey()); seen = true; } else { viewerInput = Collections.emptyList(); seen = false; } checkedPaths.addAll(viewerInput); if (tblOutputs != null && !tblOutputs.isDisposed() && vwrOutputs != null) { vwrOutputs.setInput(viewerInput); vwrOutputs.setAllChecked(true); } IWizardContainer container = getContainer(); if (container != null) container.updateButtons(); } } }; public WorkspacePreviewPage() { super("preview"); } @Override public void createControl(Composite parent) { setTitle("Preview Changes"); setImageDescriptor(Plugin.imageDescriptorFromPlugin("icons/bndtools-wizban.png")); //$NON-NLS-1$ imgAdded = AbstractUIPlugin.imageDescriptorFromPlugin(Plugin.PLUGIN_ID, "icons/incoming.gif").createImage(parent.getDisplay()); imgOverwrite = AbstractUIPlugin.imageDescriptorFromPlugin(Plugin.PLUGIN_ID, "icons/conflict.gif").createImage(parent.getDisplay()); imgError = AbstractUIPlugin.imageDescriptorFromPlugin(Plugin.PLUGIN_ID, "icons/error_obj.gif").createImage(parent.getDisplay()); int columns = 4; Composite composite = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(columns, false); composite.setLayout(layout); setControl(composite); Label lblTitle = new Label(composite, SWT.NONE); lblTitle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, columns, 1)); // Table tblOutputs = new Table(composite, SWT.BORDER | SWT.CHECK); tblOutputs.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, columns, 1)); vwrOutputs = new CheckboxTableViewer(tblOutputs); vwrOutputs.setContentProvider(ArrayContentProvider.getInstance()); vwrOutputs.setLabelProvider(new StyledCellLabelProvider() { @Override public void update(ViewerCell cell) { StyledString label; Image icon; String path = (String) cell.getElement(); String error = resourceErrors.get(path); if (error != null) { label = new StyledString(path, ItalicStyler.INSTANCE_ERROR); icon = imgError; } else { label = new StyledString(path); icon = existingFiles.contains(path) ? imgOverwrite : imgAdded; } cell.setText(path); cell.setStyleRanges(label.getStyleRanges()); cell.setImage(icon); } }); vwrOutputs.setSorter(new ViewerSorter(Collator.getInstance())); // Details display final Label lblDetails = new Label(composite, SWT.NONE); lblDetails.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, columns, 1)); lblDetails.setText(MSG_NOTHING_SELECTED); // Button Panel Label spacer1 = new Label(composite, SWT.NONE); spacer1.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); Button btnSelectNonConflict = new Button(composite, SWT.PUSH); btnSelectNonConflict.setText("Select Non-Conflicting"); btnSelectNonConflict.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { modifyLock.modifyOperation(new Runnable() { @Override public void run() { checkedPaths.clear(); for (Entry<String,Resource> entry : templateOutputs.entries()) { String path = entry.getKey(); if (!existingFiles.contains(path)) checkedPaths.add(path); } vwrOutputs.setCheckedElements(checkedPaths.toArray()); } }); } }); Button btnSelectAll = new Button(composite, SWT.PUSH); btnSelectAll.setText("Select All"); btnSelectAll.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { vwrOutputs.setAllChecked(true); } }); Button btnSelectNone = new Button(composite, SWT.PUSH); btnSelectNone.setText("Select None"); btnSelectNone.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { vwrOutputs.setAllChecked(false); } }); // Listeners vwrOutputs.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection sel = (IStructuredSelection) vwrOutputs.getSelection(); if (sel.isEmpty()) { lblDetails.setText(MSG_NOTHING_SELECTED); } else { String path = (String) sel.getFirstElement(); String resourceError = resourceErrors.get(path); if (resourceError != null) { lblDetails.setText(resourceError); } else if (existingFiles.contains(path)) { lblDetails.setText("This file already exists and will be overwritten"); } else { lblDetails.setText("This file will be created"); } } } }); vwrOutputs.addCheckStateListener(new ICheckStateListener() { @Override public void checkStateChanged(final CheckStateChangedEvent event) { modifyLock.ifNotModifying(new Runnable() { @Override public void run() { final String updatedPath = (String) event.getElement(); if (event.getChecked()) { checkedPaths.add(updatedPath); // Check any directories that are parents of this path modifyLock.modifyOperation(new Runnable() { @Override public void run() { for (Entry<String,Resource> entry : templateOutputs.entries()) { String path = entry.getKey(); if (path.endsWith("/") && updatedPath.startsWith(path)) { checkedPaths.add(path); vwrOutputs.setChecked(path, true); } } } }); } else { checkedPaths.remove(updatedPath); // Uncheck any paths that are descendants of this path if (updatedPath.endsWith("/")) { modifyLock.modifyOperation(new Runnable() { @Override public void run() { for (Entry<String,Resource> entry : templateOutputs.entries()) { String path = entry.getKey(); if (path.startsWith(updatedPath)) { checkedPaths.remove(path); vwrOutputs.setChecked(path, false); } } } }); } } } }); } }); } @Override public void dispose() { super.dispose(); imgAdded.dispose(); imgOverwrite.dispose(); imgError.dispose(); } @Override public boolean isPageComplete() { return super.isPageComplete() && seen && errorMessage == null && resourceErrors.isEmpty(); } @Override public void setVisible(boolean visible) { super.setVisible(visible); if (visible) { try { getContainer().run(true, true, calculatePreviewTask); } catch (InvocationTargetException e) { errorMessage = e.getTargetException().getMessage(); } catch (InterruptedException e) { errorMessage = e.getMessage(); } updateDisplayTask.run(); } } public void setTemplate(Template template) { // set the template and remove the resource lock synchronized (lock) { this.template = template; this.templateOutputs = null; } updateDisplayTask.run(); } public void setTargetDir(File file) { synchronized (lock) { this.targetDir = file; this.templateOutputs = null; } updateDisplayTask.run(); } public Template getTemplate() { return template; } public ResourceMap getTemplateOutputs() { synchronized (lock) { return templateOutputs; } } public File getTargetDir() { return targetDir; } public Set<String> getCheckedPaths() { return Collections.unmodifiableSet(checkedPaths); } private boolean isEqualContent(File file, InputStream content) throws IOException { byte[] a = IO.read(file); byte[] b = IO.read(content); return Arrays.equals(a, b); } }