blob: 3c4d90ec3463a6f49134dd9755b24ab341d13902 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2013 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 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:
* IBM Corporation - initial API and implementation
* Ericsson AB, Hamdan Msheik - Bug 389564
*******************************************************************************/
package org.eclipse.ant.internal.ui.preferences;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import org.eclipse.ant.core.IAntClasspathEntry;
import org.eclipse.ant.internal.core.IAntCoreConstants;
import org.eclipse.ant.internal.ui.AntUIPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.StatusDialog;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.FileSystemElement;
import org.eclipse.ui.externaltools.internal.ui.TreeAndListGroup;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.model.WorkbenchViewerComparator;
import org.eclipse.ui.wizards.datatransfer.FileSystemStructureProvider;
import org.eclipse.ui.wizards.datatransfer.IImportStructureProvider;
import org.eclipse.ui.wizards.datatransfer.ZipFileStructureProvider;
import com.ibm.icu.text.MessageFormat;
public class AddCustomDialog extends StatusDialog {
private ZipFileStructureProvider providerCache;
private IImportStructureProvider currentProvider;
// A boolean to indicate if the user has typed anything
private boolean entryChanged = false;
private Combo sourceNameField;
private List<IAntClasspathEntry> libraryEntries;
private List<String> existingNames;
private String noNameErrorMsg;
private String alreadyExistsErrorMsg;
private TreeAndListGroup selectionGroup;
private Text nameField;
private String name = IAntCoreConstants.EMPTY_STRING;
private IAntClasspathEntry library = null;
private String className = IAntCoreConstants.EMPTY_STRING;
private boolean editing = false;
private String helpContext;
/**
* Creates a new dialog with the given shell and title.
*/
public AddCustomDialog(Shell parent, List<IAntClasspathEntry> libraryEntries, List<String> existingNames, String helpContext) {
super(parent);
this.libraryEntries = libraryEntries;
this.existingNames = existingNames;
this.helpContext = helpContext;
setShellStyle(getShellStyle() | SWT.RESIZE);
}
@Override
protected Control createDialogArea(Composite parent) {
Composite topComposite = (Composite) super.createDialogArea(parent);
topComposite.setSize(topComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT));
Composite topGroup = new Composite(topComposite, SWT.NONE);
GridLayout layout = new GridLayout();
layout.numColumns = 2;
layout.marginHeight = 0;
layout.marginWidth = 0;
topGroup.setLayout(layout);
topGroup.setFont(topComposite.getFont());
topGroup.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL));
createNameGroup(topGroup);
createRootDirectoryGroup(topGroup);
createFileSelectionGroup(topComposite);
if (library != null) {
setSourceName(library.getLabel());
}
return topComposite;
}
private void createNameGroup(Composite topComposite) {
Label label = new Label(topComposite, SWT.NONE);
label.setFont(topComposite.getFont());
label.setText(AntPreferencesMessages.AddCustomDialog__Name__3);
nameField = new Text(topComposite, SWT.BORDER);
GridData data = new GridData(GridData.FILL_HORIZONTAL);
data.widthHint = IDialogConstants.ENTRY_FIELD_WIDTH;
nameField.setLayoutData(data);
nameField.setFont(topComposite.getFont());
nameField.setText(name);
nameField.addModifyListener(e -> updateStatus());
}
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell, helpContext);
}
/**
* Clears the cached structure provider after first finalizing it properly.
*/
private void clearProviderCache() {
if (providerCache != null) {
closeZipFile(providerCache.getZipFile());
providerCache = null;
}
}
/**
* Attempts to close the passed zip file, and answers a boolean indicating success.
*/
private boolean closeZipFile(ZipFile file) {
try {
file.close();
}
catch (IOException e) {
AntUIPlugin.log(MessageFormat.format(AntPreferencesMessages.AddCustomDialog_Could_not_close_zip_file__0__4, new Object[] {
file.getName() }), e);
return false;
}
return true;
}
/**
* Create the group for creating the root directory
*/
private void createRootDirectoryGroup(Composite parent) {
Label groupLabel = new Label(parent, SWT.NONE);
groupLabel.setText(AntPreferencesMessages.AddCustomDialog__Location);
groupLabel.setFont(parent.getFont());
// source name entry field
sourceNameField = new Combo(parent, SWT.BORDER | SWT.READ_ONLY);
GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL);
data.widthHint = IDialogConstants.ENTRY_FIELD_WIDTH;
sourceNameField.setLayoutData(data);
sourceNameField.setFont(parent.getFont());
sourceNameField.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updateFromSourceField();
}
});
for (IAntClasspathEntry entry : libraryEntries) {
sourceNameField.add(entry.getLabel());
}
sourceNameField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
// If there has been a key pressed then mark as dirty
entryChanged = true;
}
});
sourceNameField.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
// Clear the flag to prevent constant update
if (entryChanged) {
entryChanged = false;
updateFromSourceField();
}
}
});
}
/**
* Update the receiver from the source name field.
*/
private void updateFromSourceField() {
setSourceName(sourceNameField.getText());
updateStatus();
}
/**
* Check the field values and display a message in the status if needed.
*/
private void updateStatus() {
StatusInfo status = new StatusInfo();
String customName = nameField.getText().trim();
if (customName.length() == 0) {
status.setError(noNameErrorMsg);
} else if (!editing) {
for (String aName : existingNames) {
if (aName.equals(customName)) {
status.setError(MessageFormat.format(alreadyExistsErrorMsg, new Object[] { customName }));
updateStatus(status);
return;
}
}
}
if (selectionGroup.getListTableSelection().isEmpty()) {
status.setError(AntPreferencesMessages.AddCustomDialog_mustSelect);
}
updateStatus(status);
}
/**
* Sets the source name of the import to be the supplied path. Adds the name of the path to the list of items in the source combo and selects it.
*
* @param path
* the path to be added
*/
private void setSourceName(String path) {
if (path.length() > 0) {
String[] currentItems = this.sourceNameField.getItems();
int selectionIndex = -1;
for (int i = 0; i < currentItems.length; i++) {
if (currentItems[i].equals(path)) {
selectionIndex = i;
break;
}
}
if (selectionIndex < 0) {
int oldLength = currentItems.length;
String[] newItems = new String[oldLength + 1];
System.arraycopy(currentItems, 0, newItems, 0, oldLength);
newItems[oldLength] = path;
this.sourceNameField.setItems(newItems);
selectionIndex = oldLength;
}
this.sourceNameField.select(selectionIndex);
resetSelection();
}
}
/*
* Create the file selection widget
*/
private void createFileSelectionGroup(Composite parent) {
// Just create with a dummy root.
FileSystemElement dummyRoot = new FileSystemElement("Dummy", null, true); //$NON-NLS-1$
this.selectionGroup = new TreeAndListGroup(parent, dummyRoot, getFolderProvider(), new WorkbenchLabelProvider(), getFileProvider(), new WorkbenchLabelProvider(), SWT.NONE, 400, 150, false);
ISelectionChangedListener listener = event -> updateStatus();
WorkbenchViewerComparator comparator = new WorkbenchViewerComparator();
this.selectionGroup.setTreeComparator(comparator);
this.selectionGroup.setListSorter(comparator);
this.selectionGroup.addSelectionChangedListener(listener);
selectionGroup.addDoubleClickListener(event -> {
if (getButton(IDialogConstants.OK_ID).isEnabled()) {
buttonPressed(IDialogConstants.OK_ID);
}
});
}
/**
* Returns whether the specified source currently exists and is valid (ie.- proper format)
*/
protected boolean ensureSourceIsValid() {
ZipFile specifiedFile = getSpecifiedSourceFile();
if (specifiedFile == null) {
return false;
}
return closeZipFile(specifiedFile);
}
/**
* Answer the root FileSystemElement that represents the contents of the currently-specified .zip file. If this FileSystemElement is not currently
* defined then create and return it.
*/
private MinimizedFileSystemElement getFileSystemTree() {
IImportStructureProvider provider = null;
MinimizedFileSystemElement element = null;
ZipFile sourceFile = getSpecifiedSourceFile();
if (sourceFile == null) {
File file = new File(sourceNameField.getText());
if (file.exists()) {
provider = FileSystemStructureProvider.INSTANCE;
element = selectFiles(file, provider);
}
} else {
// zip file set as location
provider = getStructureProvider(sourceFile);
element = selectFiles(((ZipFileStructureProvider) provider).getRoot(), provider);
}
this.currentProvider = provider;
return element;
}
/**
* Invokes a file selection operation using the specified file system and structure provider. If the user specifies files then this selection is
* cached for later retrieval and is returned.
*/
private MinimizedFileSystemElement selectFiles(final Object rootFileSystemObject, final IImportStructureProvider structureProvider) {
final MinimizedFileSystemElement[] results = new MinimizedFileSystemElement[1];
BusyIndicator.showWhile(getShell().getDisplay(), () -> results[0] = createRootElement(rootFileSystemObject, structureProvider));
return results[0];
}
/**
* Creates and returns a <code>MinimizedFileSystemElement</code> if the specified file system object merits one.
*/
private MinimizedFileSystemElement createRootElement(Object fileSystemObject, IImportStructureProvider provider) {
boolean isContainer = provider.isFolder(fileSystemObject);
String elementLabel = provider.getLabel(fileSystemObject);
// Use an empty label so that display of the element's full name
// doesn't include a confusing label
MinimizedFileSystemElement dummyParent = new MinimizedFileSystemElement(IAntCoreConstants.EMPTY_STRING, null, true);
dummyParent.setPopulated();
MinimizedFileSystemElement result = new MinimizedFileSystemElement(elementLabel, dummyParent, isContainer);
result.setFileSystemObject(fileSystemObject);
// Get the files for the element so as to build the first level
result.getFiles(provider);
return dummyParent;
}
/**
* Answer a handle to the zip file currently specified as being the source. Return <code>null</code> if this file does not exist or is not of
* valid format.
*/
private ZipFile getSpecifiedSourceFile() {
try {
String expanded = sourceNameField.getText();
expanded = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(expanded);
return new ZipFile(expanded);
}
catch (ZipException e) {
StatusInfo status = new StatusInfo();
status.setError(AntPreferencesMessages.AddCustomDialog_Bad_Format);
updateStatus(status);
}
catch (IOException e) {
StatusInfo status = new StatusInfo();
status.setError(AntPreferencesMessages.AddCustomDialog_Unreadable);
updateStatus(status);
}
catch (CoreException e) {
StatusInfo status = new StatusInfo();
status.setError(AntPreferencesMessages.AddCustomDialog_13);
updateStatus(status);
}
sourceNameField.setFocus();
return null;
}
/**
* Returns a structure provider for the specified zip file.
*/
private ZipFileStructureProvider getStructureProvider(ZipFile targetZip) {
if (providerCache == null) {
providerCache = new ZipFileStructureProvider(targetZip);
} else if (!providerCache.getZipFile().getName().equals(targetZip.getName())) {
clearProviderCache();
// ie.- new value, so finalize & remove old value
providerCache = new ZipFileStructureProvider(targetZip);
} else if (!providerCache.getZipFile().equals(targetZip)) {
closeZipFile(targetZip); // ie.- duplicate handle to same .zip
}
return providerCache;
}
/**
* Repopulate the view based on the currently entered directory.
*/
private void resetSelection() {
MinimizedFileSystemElement currentRoot = getFileSystemTree();
selectionGroup.setRoot(currentRoot);
if (className.length() != 0) {
StringTokenizer tokenizer = new StringTokenizer(className, "."); //$NON-NLS-1$
selectClass(currentRoot, tokenizer);
}
}
private void selectClass(MinimizedFileSystemElement currentParent, StringTokenizer tokenizer) {
if (!tokenizer.hasMoreTokens()) {
return;
}
List<MinimizedFileSystemElement> folders = currentParent.getFolders(currentProvider);
if (folders.size() == 1) {
MinimizedFileSystemElement element = folders.get(0);
if (element.getLabel(null).equals("/")) { //$NON-NLS-1$
selectionGroup.selectAndRevealFolder(element);
selectClass(element, tokenizer);
return;
}
}
String currentName = tokenizer.nextToken();
if (tokenizer.hasMoreTokens()) {
Iterator<MinimizedFileSystemElement> allFolders = folders.iterator();
while (allFolders.hasNext()) {
MinimizedFileSystemElement folder = allFolders.next();
if (folder.getLabel(null).equals(currentName)) {
selectionGroup.selectAndRevealFolder(folder);
selectClass(folder, tokenizer);
return;
}
}
} else {
List<MinimizedFileSystemElement> files = currentParent.getFiles(currentProvider);
for (MinimizedFileSystemElement file : files) {
if (file.getLabel(null).equals(currentName + ".class")) { //$NON-NLS-1$
selectionGroup.selectAndRevealFile(file);
return;
}
}
}
}
/**
* Returns a content provider for <code>MinimizedFileSystemElement</code>s that returns only files as children.
*/
private ITreeContentProvider getFileProvider() {
return new WorkbenchContentProvider() {
@Override
public Object[] getChildren(Object o) {
if (o instanceof MinimizedFileSystemElement) {
MinimizedFileSystemElement element = (MinimizedFileSystemElement) o;
return element.getFiles(currentProvider).toArray();
}
return new Object[0];
}
};
}
/**
* Returns a content provider for <code>MinimizedFileSystemElement</code>s that returns only folders as children.
*/
private ITreeContentProvider getFolderProvider() {
return new WorkbenchContentProvider() {
@Override
public Object[] getChildren(Object o) {
if (o instanceof MinimizedFileSystemElement) {
MinimizedFileSystemElement element = (MinimizedFileSystemElement) o;
return element.getFolders(currentProvider).toArray();
}
return new Object[0];
}
@Override
public boolean hasChildren(Object o) {
if (o instanceof MinimizedFileSystemElement) {
MinimizedFileSystemElement element = (MinimizedFileSystemElement) o;
if (element.isPopulated()) {
return getChildren(element).length > 0;
}
// If we have not populated then wait until asked
return true;
}
return false;
}
};
}
@Override
protected void cancelPressed() {
clearProviderCache();
super.cancelPressed();
}
@Override
protected void okPressed() {
clearProviderCache();
name = nameField.getText().trim();
library = libraryEntries.get(sourceNameField.getSelectionIndex());
IStructuredSelection selection = this.selectionGroup.getListTableSelection();
MinimizedFileSystemElement element = (MinimizedFileSystemElement) selection.getFirstElement();
if (element == null) {
super.okPressed();
return;
}
Object file = element.getFileSystemObject();
if (file instanceof ZipEntry) {
className = ((ZipEntry) file).getName();
} else {
className = ((File) file).getAbsolutePath();
IPath classPath = new Path(className);
IPath libraryPath = null;
try {
libraryPath = new Path(URIUtil.toURL(URIUtil.toURI(library.getEntryURL())).getPath());
}
catch (MalformedURLException e) {
AntUIPlugin.log(e);
}
catch (URISyntaxException e) {
AntUIPlugin.log(e);
}
int matching = classPath.matchingFirstSegments(libraryPath);
classPath = classPath.removeFirstSegments(matching);
classPath = classPath.setDevice(null);
className = classPath.toString();
}
int index = className.lastIndexOf('.');
className = className.substring(0, index);
className = className.replace('/', '.');
super.okPressed();
}
protected String getName() {
return name;
}
protected void setName(String name) {
this.name = name;
}
protected void setLibraryEntry(IAntClasspathEntry library) {
this.library = library;
editing = true;
}
protected IAntClasspathEntry getLibraryEntry() {
return this.library;
}
protected String getClassName() {
return className;
}
protected void setClassName(String className) {
this.className = className;
}
@Override
public void create() {
super.create();
getButton(IDialogConstants.OK_ID).setEnabled(!(library == null));
}
protected void setAlreadyExistsErrorMsg(String alreadyExistsErrorMsg) {
this.alreadyExistsErrorMsg = alreadyExistsErrorMsg;
}
protected void setNoNameErrorMsg(String noNameErrorMsg) {
this.noNameErrorMsg = noNameErrorMsg;
}
}