# HG changeset patch # User Vladimir Kvashin # Date 1394605226 -14400 # Node ID c0026876594580751dd74b36eb95b3afc20b995d # Parent f5abf3ff69e2a81ebb9ae328fd3b62b7d28b8ff4 fixed #242099 - org.netbeans.modules.remote.impl.fs.RemoteFileSystemTransport.readDirectory: LowPerformance took 54148 ms. diff -r f5abf3ff69e2 -r c00268765945 dlight.remote/src/org/netbeans/modules/remote/api/ui/FileChooserBuilder.java --- a/dlight.remote/src/org/netbeans/modules/remote/api/ui/FileChooserBuilder.java Mon Mar 10 17:01:48 2014 +0100 +++ b/dlight.remote/src/org/netbeans/modules/remote/api/ui/FileChooserBuilder.java Wed Mar 12 10:20:26 2014 +0400 @@ -49,6 +49,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; @@ -64,6 +65,8 @@ import javax.swing.plaf.FileChooserUI; import org.netbeans.modules.nativeexecution.api.ExecutionEnvironment; import org.netbeans.modules.nativeexecution.api.ExecutionEnvironmentFactory; +import org.netbeans.modules.nativeexecution.api.util.ConnectionManager; +import org.netbeans.modules.nativeexecution.api.util.HostInfoUtils; import org.netbeans.modules.remote.spi.FileSystemProvider; import org.netbeans.modules.remote.support.RemoteLogger; import org.openide.filesystems.FileObject; @@ -96,6 +99,22 @@ public abstract void setCurrentDirectory(FileObject dir); public abstract FileObject getSelectedFileObject(); public abstract FileObject[] getSelectedFileObjects(); + + /** + * Very close to getHomeDirectory, but isn't quite the same: + * getHomePath() + * - does not create file object, + * - does not fire event, + * - is fast (in the case remote host info isn't avaliable, just returns "/") + * @return user home + */ + /*package*/ abstract String getHomePath(); + + /** + * Used to determine whether "~" should be expanded to home directory + * @return + */ + /*package*/ public abstract boolean isUnix(); @Override public final File getCurrentDirectory() { @@ -228,6 +247,16 @@ } @Override + public String getHomePath() { + return System.getProperty("user.home"); + } + + @Override + public boolean isUnix() { + return Utilities.isUnix(); + } + + @Override public void setCurrentDirectory(FileObject dir) { if (dir != null && dir.isFolder()) { File file = FileUtil.toFile(dir); @@ -349,6 +378,24 @@ return result.toArray(new FileObject[result.size()]); } } + + @Override + public String getHomePath() { + if (HostInfoUtils.isHostInfoAvailable(env)) { + try { + return HostInfoUtils.getHostInfo(env).getUserDir(); + } catch (IOException ex) { + RemoteLogger.finest(ex); + } catch (ConnectionManager.CancellationException ex) { + } + } + return "/"; //NOI18N + } + + @Override + public boolean isUnix() { + return true; + } @Override protected void setup(FileSystemView view) { diff -r f5abf3ff69e2 -r c00268765945 dlight.remote/src/org/netbeans/modules/remote/api/ui/FileChooserUIImpl.java --- a/dlight.remote/src/org/netbeans/modules/remote/api/ui/FileChooserUIImpl.java Mon Mar 10 17:01:48 2014 +0100 +++ b/dlight.remote/src/org/netbeans/modules/remote/api/ui/FileChooserUIImpl.java Wed Mar 12 10:20:26 2014 +0400 @@ -68,6 +68,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; @@ -95,6 +96,7 @@ import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import javax.swing.AbstractAction; import javax.swing.AbstractListModel; import javax.swing.Action; @@ -136,7 +138,6 @@ import javax.swing.filechooser.FileSystemView; import javax.swing.filechooser.FileView; import javax.swing.plaf.ActionMapUIResource; -import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicFileChooserUI; import javax.swing.tree.DefaultMutableTreeNode; @@ -157,13 +158,15 @@ import org.openide.util.NbPreferences; import org.openide.util.RequestProcessor; import org.openide.util.Utilities; +import sun.awt.shell.ShellFolder; +import sun.swing.FilePane; /** * An implementation of a customized filechooser. * * @author Soot Phengsy, inspired by Jeff Dinkins' Swing version */ -class FileChooserUIImpl extends BasicFileChooserUI{ +final class FileChooserUIImpl extends BasicFileChooserUI{ static final String USE_SHELL_FOLDER = "FileChooser.useShellFolder";//NOI18N static final String NB_USE_SHELL_FOLDER = "nb.FileChooser.useShellFolder";//NOI18N @@ -184,6 +187,7 @@ private static final RequestProcessor COMMON_RP = new RequestProcessor("Cnd File Chooser Common Worker", 16); // NOI18N private static final RequestProcessor UPDATE_RP = new RequestProcessor("Cnd File Chooser Update Worker"); // NOI18N + private static final RequestProcessor APPROVE_RP = new RequestProcessor("Cnd File Chooser Update Worker"); // NOI18N private static final String TIMEOUT_KEY="nb.fileChooser.timeout"; // NOI18N @@ -276,17 +280,23 @@ private final RequestProcessor.Task listFilesTask = UPDATE_RP.create(listFilesWorker); private volatile File curDir; - public static ComponentUI createUI(JComponent c) { - return new FileChooserUIImpl((JFileChooserEx) c); - } + private final Action approveSelectionAction; + private final Action cancelSelectionAction; + private final AtomicBoolean cancelled = new AtomicBoolean(false); + + private FileFilter actualFileFilter = null; + private GlobFilter globFilter = null; public FileChooserUIImpl(FileChooserBuilder.JFileChooserEx filechooser) { super(filechooser); + approveSelectionAction = new ApproveSelectionAction(); + cancelSelectionAction = new CancelSelectionAction(); } - + @Override public void installUI(JComponent c) { super.installUI(c); + fileChooser = (JFileChooserEx) c; } @Override @@ -1409,7 +1419,7 @@ AbstractAction escAction = new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { - getFileChooser().cancelSelection(); + getCancelSelectionAction().actionPerformed(e); } @Override public boolean isEnabled(){ @@ -2329,17 +2339,9 @@ // #105801: completionPopup might not be ready when updateCompletions not called (empty text field) if (completionPopup != null && !completionPopup.isVisible()) { if (keyCode == KeyEvent.VK_ENTER) { - File file = getFileChooser().getFileSystemView().createFileObject(filenameTextField.getText()); - if(file.exists() && file.isDirectory()) { - setSelected(new File[] {file}); - fileChooser.approveSelection(); - if (file.getParentFile() == null) { - // this will hopefully prevent popup to take inappropriate action - evt.consume(); - } - } - } - + getApproveSelectionAction().actionPerformed(new ActionEvent(evt.getSource(), evt.getID(), "")); //NOI18N + evt.consume(); + } if ((keyCode == KeyEvent.VK_TAB || keyCode == KeyEvent.VK_DOWN) || (keyCode == KeyEvent.VK_RIGHT && (filenameTextField.getCaretPosition() >= (filenameTextField.getDocument().getLength() - 1)))) { @@ -3109,5 +3111,444 @@ } } // end of UpdateWorker + + @Override + public Action getApproveSelectionAction() { + return approveSelectionAction; + } + + @Override + public Action getCancelSelectionAction() { + return cancelSelectionAction; + } + + private class CancelSelectionAction extends AbstractAction { + public void actionPerformed(ActionEvent e) { + cancelled.set(true); + getFileChooser().cancelSelection(); + } + } + + private class ApproveSelectionAction extends AbstractAction { + + protected ApproveSelectionAction() { + super(FilePane.ACTION_APPROVE_SELECTION); + } + + @Override + public void actionPerformed(final ActionEvent e) { + // most code here (and the following "if" is copied from + // BasicFileChoooserUI.ApproveSelectionAction.actionPerformed + // and adapted for our case + if (isDirectorySelected()) { + // exactly from BasicFileChoooserUI.ApproveSelectionAction.actionPerformed + File dir = getDirectory(); + if (dir != null) { + try { + // Strip trailing ".." + dir = ShellFolder.getNormalizedFile(dir); + } catch (IOException ex) { + // Ok, use f as is + } + changeDirectory(dir); + return; + } + } + + String filename = getFileName(); + + if (filename != null) { + // VK: why isn't it just trim() ? - do we really need leading spaces?? + // Remove whitespaces from end of filename + int i = filename.length() - 1; + while (i >=0 && filename.charAt(i) <= ' ') { + i--; + } + filename = filename.substring(0, i + 1); + } + + if (filename == null || filename.length() == 0) { + // no file selected, multiple selection off, therefore cancel the approve action + resetGlobFilter(); + return; + } + + // Unix: Resolve '~' to user's home directory + if (fileChooser.isUnix()) { + if (filename.startsWith("~/")) { + filename = fileChooser.getHomePath() + filename.substring(1); + } else if (filename.equals("~")) { + filename = fileChooser.getHomePath(); + } + } + + // in the case of single selectiom, use selectedFiles.get(0) + final List selectedFiles = new ArrayList<>(); + + enableAllButCancel(false); + ApproveSelectionFinisher finisher = new ApproveSelectionFinisher(e, filename, selectedFiles, cancelled); + ApproveSelectionThreadWorker worker = new ApproveSelectionThreadWorker( + e, filename, fileChooser.isMultiSelectionEnabled(), + fileChooser.getCurrentDirectory(), fileChooser.getFileSystemView(), + selectedFiles, finisher); + APPROVE_RP.post(worker); + } + } + + /** + * To be called OUT of EDT to perform long selection approval tasks. + * Upon finishing its work, calls ApproveSelectionFinisher in EDT + */ + + private static class ApproveSelectionThreadWorker implements Runnable { + + private final ActionEvent e; + private final String filename; + private final boolean multySelection; + private final File currentDir; + private final FileSystemView fs; + private final List selectedFiles; + private final ApproveSelectionFinisher finisher; + + public ApproveSelectionThreadWorker(ActionEvent e, String filename, boolean multySelection, File currentDir, FileSystemView fs, List selectedFiles, ApproveSelectionFinisher finisher) { + this.e = e; + this.filename = filename; + this.multySelection = multySelection; + this.currentDir = currentDir; + this.fs = fs; + this.selectedFiles = selectedFiles; + this.finisher = finisher; + } + + @Override + public void run() { + try { + if (multySelection && filename.length() > 1 && + filename.charAt(0) == '"' && filename.charAt(filename.length() - 1) == '"') { + + // VK: double space between \" breaks this?! + String[] files = filename.substring(1, filename.length() - 1).split("\" \""); + // Optimize searching files by names in "children" array + Arrays.sort(files); + + File[] children = null; + int childIndex = 0; + + for (String str : files) { + File file = fs.createFileObject(str); + if (!file.isAbsolute()) { + if (children == null) { + children = fs.getFiles(currentDir, false); + Arrays.sort(children); + } + for (int k = 0; k < children.length; k++) { + int l = (childIndex + k) % children.length; + if (children[l].getName().equals(str)) { + file = children[l]; + childIndex = l + 1; + break; + } + } + } + selectedFiles.add(file); + } + } else { + File selectedFile = fs.createFileObject(filename); + if (!selectedFile.isAbsolute()) { + selectedFile = fs.getChild(currentDir, filename); + } + selectedFiles.add(selectedFile); + } + } finally { + SwingUtilities.invokeLater(finisher); + } + } + } + + /** + * To be called in EDT to complete selection approval + */ + private class ApproveSelectionFinisher implements Runnable { + + private final String filename; + private final List selectedFiles; + private final ActionEvent e; + private final AtomicBoolean cancelled; + + public ApproveSelectionFinisher(ActionEvent e, String filename, List selectedFiles, AtomicBoolean cancelled) { + this.e = e; + this.selectedFiles = selectedFiles; + this.filename = filename; + this.cancelled = cancelled; + } + + @Override + public void run() { + + if (cancelled.get()) { + return; + } + + enableAllButCancel(true); + + JFileChooser chooser = getFileChooser(); + resetGlobFilter(); + + if (selectedFiles.size() == 1) { + + File selectedFile = selectedFiles.get(0); + + // check for wildcard pattern + FileFilter currentFilter = chooser.getFileFilter(); + if (!selectedFile.exists() && isGlobPattern(filename)) { + changeDirectory(selectedFile.getParentFile()); + if (globFilter == null) { + globFilter = new GlobFilter(); + } + try { + globFilter.setPattern(selectedFile.getName()); + if (!(currentFilter instanceof GlobFilter)) { + actualFileFilter = currentFilter; + } + chooser.setFileFilter(null); + chooser.setFileFilter(globFilter); + return; + } catch (PatternSyntaxException pse) { + // Not a valid glob pattern. Abandon filter. + } + } + + // Check for directory change action + boolean isDir = (selectedFile != null && selectedFile.isDirectory()); + boolean isTrav = (selectedFile != null && chooser.isTraversable(selectedFile)); + boolean isDirSelEnabled = chooser.isDirectorySelectionEnabled(); + boolean isFileSelEnabled = chooser.isFileSelectionEnabled(); + boolean isCtrl = (e != null && (e.getModifiers() & + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) != 0); + + if (isDir && isTrav && (isCtrl || !isDirSelEnabled)) { + changeDirectory(selectedFile); + return; + } else if ((isDir || !isFileSelEnabled) + && (!isDir || !isDirSelEnabled) + && (!isDirSelEnabled || selectedFile.exists())) { + selectedFiles.clear(); + } + } + + + if (!selectedFiles.isEmpty()) { + if (chooser.isMultiSelectionEnabled()) { + final File[] selectedFilesArray = selectedFiles.toArray(new File[selectedFiles.size()]); + chooser.setSelectedFiles(selectedFilesArray); + // Do it again. This is a fix for bug 4949273 to force the + // selected value in case the ListSelectionModel clears it + // for non-existing file names. + chooser.setSelectedFiles(selectedFilesArray); + } else { + chooser.setSelectedFile(selectedFiles.get(0)); + } + chooser.approveSelection(); + } else { + if (chooser.isMultiSelectionEnabled()) { + chooser.setSelectedFiles(null); + } else { + chooser.setSelectedFile(null); + } + chooser.cancelSelection(); + } + } + } + + private void enableAllButCancel(boolean enable) { + FileChooserUIImpl.this.newFolderButton.setEnabled(enable); + FileChooserUIImpl.this.approveButton.setEnabled(enable); +// FileChooserUIImpl.this.topCombo.setEnabled(enable); +// FileChooserUIImpl.this.filenameTextField.setEditable(enable); +// FileChooserUIImpl.this.filenameTextField.setEnabled(enable); +// FileChooserUIImpl.this.filterTypeComboBox.setEnabled(enable); +// FileChooserUIImpl.this.tree.setEnabled(enable); + } + + private void changeDirectory(File dir) { + JFileChooser fc = getFileChooser(); + // Traverse shortcuts on Windows + if (dir != null && FilePane.usesShellFolder(fc)) { + try { + ShellFolder shellFolder = ShellFolder.getShellFolder(dir); + + if (shellFolder.isLink()) { + File linkedTo = shellFolder.getLinkLocation(); + + // If linkedTo is null we try to use dir + if (linkedTo != null) { + if (fc.isTraversable(linkedTo)) { + dir = linkedTo; + } else { + return; + } + } else { + dir = shellFolder; + } + } + } catch (FileNotFoundException ex) { + return; + } + } + fc.setCurrentDirectory(dir); + if (fc.getFileSelectionMode() == JFileChooser.FILES_AND_DIRECTORIES && + fc.getFileSystemView().isFileSystem(dir)) { + + setFileName(dir.getAbsolutePath()); + } + } + + private void resetGlobFilter() { + if (actualFileFilter != null) { + JFileChooser chooser = getFileChooser(); + FileFilter currentFilter = chooser.getFileFilter(); + if (currentFilter != null && currentFilter.equals(globFilter)) { + chooser.setFileFilter(actualFileFilter); + chooser.removeChoosableFileFilter(globFilter); + } + actualFileFilter = null; + } + } + + private static boolean isGlobPattern(String filename) { + return ((File.separatorChar == '\\' && (filename.indexOf('*') >= 0 + || filename.indexOf('?') >= 0)) + || (File.separatorChar == '/' && (filename.indexOf('*') >= 0 + || filename.indexOf('?') >= 0 + || filename.indexOf('[') >= 0))); + } + + + /* A file filter which accepts file patterns containing + * the special wildcards *? on Windows and *?[] on Unix. + */ + class GlobFilter extends FileFilter { + Pattern pattern; + String globPattern; + + public void setPattern(String globPattern) { + char[] gPat = globPattern.toCharArray(); + char[] rPat = new char[gPat.length * 2]; + boolean isWin32 = (File.separatorChar == '\\'); + boolean inBrackets = false; + int j = 0; + + this.globPattern = globPattern; + + if (isWin32) { + // On windows, a pattern ending with *.* is equal to ending with * + int len = gPat.length; + if (globPattern.endsWith("*.*")) { + len -= 2; + } + for (int i = 0; i < len; i++) { + switch(gPat[i]) { + case '*': + rPat[j++] = '.'; + rPat[j++] = '*'; + break; + + case '?': + rPat[j++] = '.'; + break; + + case '\\': + rPat[j++] = '\\'; + rPat[j++] = '\\'; + break; + + default: + if ("+()^$.{}[]".indexOf(gPat[i]) >= 0) { + rPat[j++] = '\\'; + } + rPat[j++] = gPat[i]; + break; + } + } + } else { + for (int i = 0; i < gPat.length; i++) { + switch(gPat[i]) { + case '*': + if (!inBrackets) { + rPat[j++] = '.'; + } + rPat[j++] = '*'; + break; + + case '?': + rPat[j++] = inBrackets ? '?' : '.'; + break; + + case '[': + inBrackets = true; + rPat[j++] = gPat[i]; + + if (i < gPat.length - 1) { + switch (gPat[i+1]) { + case '!': + case '^': + rPat[j++] = '^'; + i++; + break; + + case ']': + rPat[j++] = gPat[++i]; + break; + } + } + break; + + case ']': + rPat[j++] = gPat[i]; + inBrackets = false; + break; + + case '\\': + if (i == 0 && gPat.length > 1 && gPat[1] == '~') { + rPat[j++] = gPat[++i]; + } else { + rPat[j++] = '\\'; + if (i < gPat.length - 1 && "*?[]".indexOf(gPat[i+1]) >= 0) { + rPat[j++] = gPat[++i]; + } else { + rPat[j++] = '\\'; + } + } + break; + + default: + //if ("+()|^$.{}<>".indexOf(gPat[i]) >= 0) { + if (!Character.isLetterOrDigit(gPat[i])) { + rPat[j++] = '\\'; + } + rPat[j++] = gPat[i]; + break; + } + } + } + this.pattern = Pattern.compile(new String(rPat, 0, j), Pattern.CASE_INSENSITIVE); + } + + @Override + public boolean accept(File f) { + if (f == null) { + return false; + } + if (f.isDirectory()) { + return true; + } + return pattern.matcher(f.getName()).matches(); + } + + @Override + public String getDescription() { + return globPattern; + } + } } diff -r f5abf3ff69e2 -r c00268765945 dlight.remote/src/org/netbeans/modules/remote/api/ui/RemoteFileSystemView.java --- a/dlight.remote/src/org/netbeans/modules/remote/api/ui/RemoteFileSystemView.java Mon Mar 10 17:01:48 2014 +0100 +++ b/dlight.remote/src/org/netbeans/modules/remote/api/ui/RemoteFileSystemView.java Wed Mar 12 10:20:26 2014 +0400 @@ -51,9 +51,9 @@ import org.netbeans.modules.nativeexecution.api.util.ConnectionManager.CancellationException; import java.util.logging.Level; import javax.swing.Icon; +import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.filechooser.FileSystemView; -import org.netbeans.modules.dlight.libs.common.PathUtilities; import org.netbeans.modules.nativeexecution.api.ExecutionEnvironment; import org.netbeans.modules.nativeexecution.api.HostInfo; import org.netbeans.modules.nativeexecution.api.util.ConnectionManager; @@ -62,7 +62,6 @@ import org.netbeans.modules.remote.support.RemoteLogger; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileSystem; -import org.openide.util.Exceptions; /** * @@ -95,6 +94,13 @@ @Override public File createFileObject(String path) { + if (RemoteLogger.getInstance().isLoggable(Level.FINEST)) { + if (SwingUtilities.isEventDispatchThread()) { + RemoteLogger.finest(new IllegalStateException("RFSV: creating file in EDT " + path)); //NOI18N + } else { + RemoteLogger.getInstance().log(Level.FINEST, "RFSV: creating file for {0}", path); + } + } RemoteLogger.getInstance().log(Level.FINEST, "RFSV: creating file for {0}", path); if (!path.isEmpty() && path.charAt(0) != '/') { return factory.create(env, path); @@ -152,9 +158,9 @@ HostInfo hostInfo = HostInfoUtils.getHostInfo(env); return factory.create(env, fs.findResource(hostInfo.getUserDir())); } catch (IOException ex) { - Exceptions.printStackTrace(ex); + RemoteLogger.finest(ex); } catch (CancellationException ex) { - Exceptions.printStackTrace(ex); + // never report cancellation exception } finally { changeSupport.firePropertyChange(LOADING_STATUS, "${HOME}", null); // NOI18N }