--- a/openide.filesystems/src/org/openide/filesystems/FileUtil.java Tue Dec 10 16:54:26 2013 +0000 +++ a/openide.filesystems/src/org/openide/filesystems/FileUtil.java Wed Dec 11 01:15:07 2013 +0200 @@ -111,7 +111,7 @@ private static byte[] ZIP_HEADER_1 = {0x50, 0x4b, 0x03, 0x04}; /** Also seems to be used at least in apisupport/project/test/unit/data/example-external-projects/suite3/nbplatform/random/modules/ext/stuff.jar; not known why */ private static byte[] ZIP_HEADER_2 = {0x50, 0x4b, 0x05, 0x06}; - + /** transient attributes which should not be copied * of type Set */ @@ -144,12 +144,12 @@ return file.getPath() + "(" + file.getClass() + ")"; // NOI18N } } - + static boolean assertNormalized(File path) { if (path != null) { File np; assert path.getClass().getName().startsWith("sun.awt.shell") || - path.equals(np = FileUtil.normalizeFileCached(path)) : + path.equals(np = FileUtil.normalizeFileCached(path)) : "Need to normalize " + toDebugString(path) + " was " + toDebugString(np); //NOI18N } return true; @@ -171,7 +171,7 @@ private FileUtil() { } - + /** * Refreshes all necessary filesystems. Not all instances of FileObject are refreshed * but just those that represent passed files and their children recursively. @@ -186,11 +186,11 @@ } catch (IOException ex) { Exceptions.printStackTrace(ex); } - } - } + } + } /** - * Refreshes all FileObject that represent files File.listRoots() + * Refreshes all FileObject that represent files File.listRoots() * and their children recursively. * @since 7.7 */ @@ -230,7 +230,7 @@ /** * Registers listener so that it will receive * FileEvents from FileSystems providing instances - * of FileObject convertible to java.io.File. + * of FileObject convertible to java.io.File. * @param fcl * @see #toFileObject * @since 7.7 @@ -242,11 +242,11 @@ fs.addFileChangeListener(fcl); } } - + /** * Unregisters listener so that it will no longer receive * FileEvents from FileSystems providing instances - * of FileObject convertible to java.io.File + * of FileObject convertible to java.io.File * @param fcl * @see #toFileObject * @since 7.7 @@ -280,7 +280,7 @@ *
  • fileRenamed event is fired when the folder is renamed or a child file/folder is renamed
  • *
  • fileAttributeChanged is fired when FileObject's attribute is changed
  • * - * Can only add a given [listener, path] pair once. However a listener can + * Can only add a given [listener, path] pair once. However a listener can * listen to any number of paths. Note that listeners are always held weakly * - if the listener is collected, it is quietly removed. * @@ -307,7 +307,7 @@ FileChangeImpl.removeFileChangeListenerImpl(LOG, listener, path); } /** - * Works like {@link #addRecursiveListener(org.openide.filesystems.FileChangeListener, java.io.File, java.io.FileFilter, java.util.concurrent.Callable) + * Works like {@link #addRecursiveListener(org.openide.filesystems.FileChangeListener, java.io.File, java.io.FileFilter, java.util.concurrent.Callable) * addRecursiveListener(listener, path, null, null)}. * * @param listener FileChangeListener to listen to changes in path @@ -319,7 +319,7 @@ addRecursiveListener(listener, path, null, null); } - /** Works like {@link #addRecursiveListener(org.openide.filesystems.FileChangeListener, java.io.File, java.io.FileFilter, java.util.concurrent.Callable) + /** Works like {@link #addRecursiveListener(org.openide.filesystems.FileChangeListener, java.io.File, java.io.FileFilter, java.util.concurrent.Callable) * addRecursiveListener(listener, path, null, stop)}. * * @param listener FileChangeListener to listen to changes in path @@ -335,7 +335,7 @@ addRecursiveListener(listener, path, null, stop); } - /** + /** * Adds a listener to changes under given path. It permits you to listen to a file * which does not yet exist, or continue listening to it after it is deleted and recreated, etc. *
    @@ -368,13 +368,13 @@ * next recursive items is interrupted. The listener may or may not get * some events from already registered folders. * - * + * * Those who provide {@link FileFilter recurseInto} callback can prevent - * the system to enter, and register operating system level listeners + * the system to enter, and register operating system level listeners * to certain subtrees under the provided path. This does * not prevent delivery of changes, if they are made via the filesystem API. * External changes however will not be detected. - * + * * @param listener FileChangeListener to listen to changes in path * @param path File path to listen to (even not existing) * @param stop an interface to interrupt the process of registering @@ -404,10 +404,10 @@ } /** - * Executes atomic action. For more info see {@link FileSystem#runAtomicAction}. + * Executes atomic action. For more info see {@link FileSystem#runAtomicAction}. *

    * All events about filesystem changes (related to events on all affected instances of FileSystem) - * are postponed after the whole atomicCode + * are postponed after the whole atomicCode * is executed. *

    * @param atomicCode code that is supposed to be run as atomic action. See {@link FileSystem#runAtomicAction} @@ -420,10 +420,10 @@ } /** - * Executes atomic action. For more info see {@link FileSystem#runAtomicAction}. + * Executes atomic action. For more info see {@link FileSystem#runAtomicAction}. *

    * All events about filesystem changes (related to events on all affected instances of FileSystem) - * are postponed after the whole atomicCode + * are postponed after the whole atomicCode * is executed. *

    * @param atomicCode code that is supposed to be run as atomic action. See {@link FileSystem#runAtomicAction} @@ -440,10 +440,10 @@ } catch (IOException ex) { Exceptions.printStackTrace(ex); } - } + } /** * Returns FileObject for a folder. - * If such a folder does not exist then it is created, including any necessary but nonexistent parent + * If such a folder does not exist then it is created, including any necessary but nonexistent parent * folders. Note that if this operation fails it may have succeeded in creating some of the necessary * parent folders. * @param folder folder to be created @@ -458,26 +458,26 @@ } if (existingFolder == null) { throw new IOException(folder.getAbsolutePath()); - } - + } + FileObject retval = null; - FileObject folderFo = FileUtil.toFileObject(existingFolder); + FileObject folderFo = FileUtil.toFileObject(existingFolder); assert folderFo != null : existingFolder.getAbsolutePath(); final String relativePath = getRelativePath(existingFolder, folder); try { - retval = FileUtil.createFolder(folderFo,relativePath); + retval = FileUtil.createFolder(folderFo,relativePath); } catch (IOException ex) { //thus retval = null; } - //if refresh needed because of external changes + //if refresh needed because of external changes if (retval == null || !retval.isValid()) { folderFo.getFileSystem().refresh(false); retval = FileUtil.createFolder(folderFo,relativePath); - } - assert retval != null; - return retval; - } - + } + assert retval != null; + return retval; + } + /**Returns FileObject for a data file. * If such a data file does not exist then it is created, including any necessary but nonexistent parent * folders. Note that if this operation fails it may have succeeded in creating some of the necessary @@ -487,33 +487,33 @@ * @throws java.io.IOException if the creation fails * @since 7.0 */ - public static FileObject createData (final File data) throws IOException { + public static FileObject createData (final File data) throws IOException { File folder = data; while(folder != null && !folder.isDirectory()) { folder = folder.getParentFile(); } if (folder == null) { throw new IOException(data.getAbsolutePath()); - } - + } + FileObject retval = null; FileObject folderFo = FileUtil.toFileObject(folder); assert folderFo != null : folder.getAbsolutePath(); final String relativePath = getRelativePath(folder, data); try { - retval = FileUtil.createData(folderFo,relativePath); + retval = FileUtil.createData(folderFo,relativePath); } catch (IOException ex) { //thus retval = null; } - //if refresh needed because of external changes + //if refresh needed because of external changes if (retval == null || !retval.isValid()) { folderFo.getFileSystem().refresh(false); retval = FileUtil.createData(folderFo,relativePath); - } - assert retval != null; + } + assert retval != null; return retval; - } - + } + private static String getRelativePath(final File dir, final File file) { Stack stack = new Stack(); File tempFile = file; @@ -528,10 +528,10 @@ if (!stack.isEmpty()) { retval.append('/');//NOI18N } - } + } return retval.toString(); } - + /** Copies stream of files. *

    * Please be aware, that this method doesn't close any of passed streams. @@ -674,10 +674,10 @@ } } - /** Returns a folder on given filesystem if such a folder exists. - * If not then a folder is created, including any necessary but nonexistent parent + /** Returns a folder on given filesystem if such a folder exists. + * If not then a folder is created, including any necessary but nonexistent parent * folders. Note that if this operation fails it may have succeeded in creating some of the necessary - * parent folders. + * parent folders. * The name of the new folder can be * specified as a multi-component pathname whose components are separated * by File.separatorChar or "/" (forward slash). @@ -732,8 +732,8 @@ return folder; } - /** Returns a data file on given filesystem if such a data file exists. - * If not then a data file is created, including any necessary but nonexistent parent + /** Returns a data file on given filesystem if such a data file exists. + * If not then a data file is created, including any necessary but nonexistent parent * folders. Note that if this operation fails it may have succeeded in creating some of the necessary * parent folders. The name of * data file can be composed as resource name (e. g. org/netbeans/myfolder/mydata ). @@ -814,7 +814,7 @@ * @since 1.29 */ public static File toFile(FileObject fo) { - File retVal = (File) fo.getAttribute("java.io.File"); // NOI18N; + File retVal = (File) fo.getAttribute("java.io.File"); // NOI18N; if (retVal == null) { URL fileURL = URLMapper.findURL(fo, URLMapper.INTERNAL); @@ -845,7 +845,7 @@ *

          * OpenIDE-Module-Needs: org.openide.filesystems.FileUtil.toFileObject
          * 
    - * + * * @param file a disk file (may or may not exist). This file * must be {@linkplain #normalizeFile normalized}. * @return a corresponding file object, or null if the file does not exist @@ -863,7 +863,7 @@ if (asserts) { File normFile = normalizeFile(file); if (!file.equals(normFile)) { - final String msg = "Parameter file was not " + // NOI18N + final String msg = "Parameter file was not " + // NOI18N "normalized. Was " + toDebugString(file) + " instead of " + toDebugString(normFile); // NOI18N LOG.log(Level.WARNING, msg); LOG.log(Level.INFO, msg, new IllegalArgumentException(msg)); @@ -893,7 +893,7 @@ } return retVal; } - + /** Finds appropriate FileObjects to java.io.File if possible. * If not possible then empty array is returned. More FileObjects may * correspond to one java.io.File that`s why array is returned. @@ -951,8 +951,8 @@ AtomicBoolean isRawValue = new AtomicBoolean(); Object value = XMLMapAttr.getRawAttribute(source, key, isRawValue); - // #132801 and #16761 - don't set attributes where value is - // instance of VoidValue because these attributes were previously written + // #132801 and #16761 - don't set attributes where value is + // instance of VoidValue because these attributes were previously written // by mistake in code. So it should happen only if you import some // settings from old version. if (value != null && !(value instanceof MultiFileObject.VoidValue)) { @@ -1310,7 +1310,7 @@ * {@link MIMEResolver#getMIMETypes} contain one or more of the requested * MIME types will be asked if they recognize the file. It is possible for * the resulting MIME type to not be a member of this list. - * @return the MIME type for the FileObject, or null if + * @return the MIME type for the FileObject, or null if * the FileObject is unrecognized. It may return {@code content/unknown} instead of {@code null}. * It is possible for the resulting MIME type to not be a member of given list. * @since 7.13 @@ -1319,7 +1319,7 @@ Parameters.notNull("withinMIMETypes", withinMIMETypes); //NOI18N return MIMESupport.findMIMEType(fo, withinMIMETypes); } - + /** Registers specified extension to be recognized as specified MIME type. * If MIME type parameter is null, it cancels previous registration. * Note that you may register a case-sensitive extension if that is @@ -1541,7 +1541,7 @@ } return normalized; } - + /** * Normalize a file path to a clean form. * This method may for example make sure that the returned file uses @@ -1563,14 +1563,14 @@ assert assertNormalized(ret); return ret; } - + private static File normalizeFileCached(final File file) { Map normalizedPaths = getNormalizedFilesMap(); String unnormalized = file.getPath(); String normalized = normalizedPaths.get(unnormalized); if ( - normalized != null && - normalized.equalsIgnoreCase(unnormalized) && + normalized != null && + normalized.equalsIgnoreCase(unnormalized) && !normalized.equals(unnormalized) ) { normalized = null; @@ -1628,7 +1628,7 @@ File retVal = file; try { - // URI.normalize removes ../ and ./ sequences nicely. + // URI.normalize removes ../ and ./ sequences nicely. File absoluteFile = Utilities.toFile(Utilities.toURI(file).normalize()); File canonicalFile = file.getCanonicalFile(); String absolutePath = absoluteFile.getAbsolutePath(); @@ -1661,7 +1661,7 @@ File retVal = File.listRoots()[0]; File pureCanonicalFile = retVal; - final String pattern = File.separator + ".." + File.separator; //NOI18N + final String pattern = File.separator + ".." + File.separator; //NOI18N final String fileName; { // strips insufficient non-".." segments preceding them @@ -1697,24 +1697,93 @@ return retVal; } - private static File normalizeFileOnWindows(final File file) { + /** + * Fast normalization of local file paths located on a drive without using IO + * + * Notes: Declared public to allow unit testing of this method + * + * @param filePath + * @throws Exception + * @return String Normalized file path + */ + public static String getNormalizedPathOnWindows(String filePath) + throws Exception { + /* Check for wildcards - Canonicalization is not globbing */ + if (filePath.contains("?") || filePath.contains("*")) + throw new Exception(); + + String[] pathParts = filePath.split("[\\\\/]"); + ArrayList normalizedPathParts; + normalizedPathParts = new ArrayList(pathParts.length); + + for (String subPath : pathParts) { + /* Skip the dot and empty(sequence of slashes) names */ + if (subPath.equals(".") || subPath.length() == 0) + continue; + + /* Do not remove drive letter and : */ + if (subPath.equals("..")) { + if (normalizedPathParts.size() > 1) + normalizedPathParts.remove(normalizedPathParts.size() - 1); + continue; + } + + /* Path chunk should not end with a dot(Except special "." and ".." notations which are specially checked above) */ + if (subPath.endsWith(".")) + throw new Exception(); + + normalizedPathParts.add(subPath); + } + + String fileName = normalizedPathParts.get(normalizedPathParts.size() - 1); + normalizedPathParts.remove(normalizedPathParts.size() - 1); + + String normalizedPath = ""; + for (String pathPart : normalizedPathParts) + normalizedPath += pathPart + File.separator; + normalizedPath += fileName; + /* Process shorts like "C:" correctly -> Shall return "C:\" */ + if (normalizedPath.length() == 2) + normalizedPath += File.separator; + + char driveLetter = normalizedPath.charAt(0); + normalizedPath = normalizedPath.replaceFirst(Character.toString(driveLetter), Character.toString(Character.toUpperCase(driveLetter))); + + return normalizedPath; + } + + private static File normalizeFileOnWindows(final File file) { File retVal = null; - + if (file.getClass().getName().startsWith("sun.awt.shell")) { // NOI18N return file; } - try { - retVal = file.getCanonicalFile(); - if (retVal.getName().equals(".")) { // NOI18Ny - // try one more time - retVal = retVal.getCanonicalFile(); + String filePath = file.getAbsolutePath(); + + /* Check if path starts with a standard drive letter */ + if (filePath.length() > 1 && filePath.charAt(1) == ':') { + try { + String normalizedPath = getNormalizedPathOnWindows(filePath); + retVal = new File(normalizedPath); + } catch (Exception e) { + String path = file.getPath(); + LOG.log(Level.FINE, path, e); } - } catch (IOException e) { - String path = file.getPath(); - // report only other than UNC path \\ or \\computerName because these cannot be canonicalized - if (!path.equals("\\\\") && !("\\\\".equals(file.getParent()))) { //NOI18N - LOG.log(Level.FINE, path, e); + } + else { + try { + retVal = file.getCanonicalFile(); + if (retVal.getName().equals(".")) { // NOI18Ny + // try one more time + retVal = retVal.getCanonicalFile(); + } + } catch (IOException e) { + String path = file.getPath(); + // report only other than UNC path \\ or \\computerName because these cannot be canonicalized + if (!path.equals("\\\\") && !("\\\\".equals(file.getParent()))) { //NOI18N + LOG.log(Level.FINE, path, e); + } } } // #135547 - on Windows Vista map "Documents and Settings\\My Documents" to "Users\\Documents" @@ -1843,7 +1912,7 @@ } return new URL(jarPath); - } catch (MalformedURLException mue) { + } catch (MalformedURLException mue) { LOG.log( Level.WARNING, "Invalid URL ({0}): {1}, jarPath: {2}", //NOI18N @@ -1963,7 +2032,7 @@ try { boolean wasDir; boolean isDir; - URL u; + URL u; do { wasDir = entry.isDirectory(); LOG.finest("urlForArchiveOrDir:toURI:entry"); //NOI18N @@ -2091,7 +2160,7 @@ Parameters.notNull("path", path); //NOI18N return Repository.getDefault().getDefaultFileSystem().findResource(path); } - + /** Finds a config object under given path. The path contains the extension * of a file e.g.: *
    @@ -2101,7 +2170,7 @@ 
          * @param filePath path to .instance or .settings file
          * @param type the requested type for given object
          * @return either null or instance of requrested type
    -     * @since 7.49 
    +     * @since 7.49
          */
         public static  T getConfigObject(String path, Class type) {
             FileObject fo = getConfigFile(path);
    @@ -2307,7 +2376,7 @@ 
                 return wrapFileNoCanonicalize(delegate.createFileObject(path));
             }
         }
    -    
    +
         private static FileSystem getDiskFileSystem() {
             synchronized (FileUtil.class) {
                 return diskFileSystem;