| /******************************************************************************* |
| * Copyright (c) 2000, 2013 IBM Corporation and others. |
| * 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: |
| * 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.DoubleClickEvent; |
| import org.eclipse.jface.viewers.IDoubleClickListener; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.ITreeContentProvider; |
| import org.eclipse.jface.viewers.SelectionChangedEvent; |
| 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.ModifyEvent; |
| import org.eclipse.swt.events.ModifyListener; |
| 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); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) |
| */ |
| @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(new ModifyListener() { |
| @Override |
| public void modifyText(ModifyEvent e) { |
| updateStatus(); |
| } |
| }); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell) |
| */ |
| @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() { |
| /* |
| * @see KeyListener.keyPressed |
| */ |
| @Override |
| public void keyPressed(KeyEvent e) { |
| // If there has been a key pressed then mark as dirty |
| entryChanged = true; |
| } |
| }); |
| |
| sourceNameField.addFocusListener(new FocusAdapter() { |
| /* |
| * @see FocusListener.focusLost(FocusEvent) |
| */ |
| @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 = new ISelectionChangedListener() { |
| @Override |
| public void selectionChanged(SelectionChangedEvent event) { |
| updateStatus(); |
| } |
| }; |
| |
| WorkbenchViewerComparator comparator = new WorkbenchViewerComparator(); |
| this.selectionGroup.setTreeComparator(comparator); |
| this.selectionGroup.setListSorter(comparator); |
| this.selectionGroup.addSelectionChangedListener(listener); |
| selectionGroup.addDoubleClickListener(new IDoubleClickListener() { |
| @Override |
| public void doubleClick(DoubleClickEvent 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; |
| } |
| }; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.dialogs.Dialog#cancelPressed() |
| */ |
| @Override |
| protected void cancelPressed() { |
| clearProviderCache(); |
| super.cancelPressed(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.dialogs.Dialog#okPressed() |
| */ |
| @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; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.window.Window#create() |
| */ |
| @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; |
| } |
| } |