All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.filesystems.FileUtil Maven / Gradle / Ivy

There is a newer version: RELEASE240
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.openide.filesystems;

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SyncFailedException;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLStreamHandler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.modules.openide.filesystems.declmime.MIMEResolverImpl;
import org.openide.filesystems.FileSystem.AtomicAction;
import org.openide.filesystems.spi.ArchiveRootProvider;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.Parameters;
import org.openide.util.RequestProcessor;
import org.openide.util.BaseUtilities;
import org.openide.util.Lookup;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
import org.openide.util.lookup.implspi.NamedServicesProvider;

/** Common utilities for handling files.
 * This is a dummy class; all methods are static.
 */
public final class FileUtil extends Object {

    private static final RequestProcessor REFRESH_RP = new RequestProcessor("FileUtil-Refresh-All");//NOI18N
    private static RequestProcessor.Task refreshTask = null;

    private static final Logger LOG = Logger.getLogger(FileUtil.class.getName());

    /** transient attributes which should not be copied
    * of type Set
    */
    static final Set transientAttributes = new HashSet();

    static {
        transientAttributes.add("templateWizardURL"); // NOI18N
        transientAttributes.add("templateWizardIterator"); // NOI18N
        transientAttributes.add("templateWizardDescResource"); // NOI18N
        transientAttributes.add("templateCategory"); // NOI18N
        transientAttributes.add("instantiatingIterator"); // NOI18N
        transientAttributes.add("instantiatingWizardURL"); // NOI18N
        transientAttributes.add("SystemFileSystem.localizingBundle"); // NOI18N
        transientAttributes.add("SystemFileSystem.icon"); // NOI18N
        transientAttributes.add("SystemFileSystem.icon32"); // NOI18N
        transientAttributes.add("displayName"); // NOI18N
        transientAttributes.add("iconBase"); // NOI18N
        transientAttributes.add("position"); // NOI18N
        transientAttributes.add(MultiFileObject.WEIGHT_ATTRIBUTE); // NOI18N
    }

    private static FileSystem diskFileSystem;

    static String toDebugString(File file) {
        if (file == null) {
            return "NULL-ref"; // NOI18N
        } else {
            return file.getPath() + "(" + file.getClass() + ")"; // NOI18N
        }
    }
    
    static boolean assertNormalized(File path) {
        return assertNormalized(path, false);
    }

    /**
     * @param path Path to compare with its normalized path.
     * @param forceIgnoreCase Force case insensitive comparison with normalized
     * path.
     *
     * return True if the path is equal to its normalized path, false otherwise.
     */
    static boolean assertNormalized(File path, boolean forceIgnoreCase) {
        if (path != null) {
            File np = null;
            assert path.getClass().getName().startsWith("sun.awt.shell") ||
                (!forceIgnoreCase && path.equals(np = FileUtil.normalizeFileCached(path))) ||
                (forceIgnoreCase && path.getPath().equalsIgnoreCase((np = FileUtil.normalizeFileCached(path)).getPath())):
                "Need to normalize " + toDebugString(path) + " was " + toDebugString(np);  //NOI18N
        }
        return true;
    }

    private static FileSystem getDiskFileSystemFor(File... files) {
        FileSystem fs = getDiskFileSystem();
        if (fs == null) {
            for (File file : files) {
                FileObject fo = toFileObject(file);
                fs = getDiskFileSystem();
                if (fs != null) {
                    break;
                }
            }
        }
        return fs;
    }

    private FileUtil() {
    }
    
    /**
     * Refreshes all necessary filesystems. Not all instances of FileObject are refreshed
     * but just those that represent passed files and their children recursively.
     * @param files
     * @since 7.6
     */
    public static void refreshFor(File... files) {
        FileSystem fs = getDiskFileSystemFor(files);
        if (fs != null) {
            try {
                fs.getRoot().setAttribute("request_for_refreshing_files_be_aware_this_is_not_public_api", files);
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            }
        } 
    }         

    /**
     * Refreshes all FileObject that represent files File.listRoots() 
     * and their children recursively.
     * @since 7.7
     */
    public static void refreshAll() {
        // run just one refreshTask in time and always wait for finish
        final RequestProcessor.Task taskToWaitFor;  // prevent possible NPE with only refreshTask.waitFinished
        synchronized (REFRESH_RP) {
            if (refreshTask != null) {
                refreshTask.cancel();
            } else {
                refreshTask = REFRESH_RP.create(new Runnable() {

                    public void run() {
                        LOG.fine("refreshAll - started");  //NOI18N
                        refreshFor(File.listRoots());
                        try {
                            getConfigRoot().getFileSystem().refresh(true);
                        } catch (FileStateInvalidException ex) {
                            Exceptions.printStackTrace(ex);
                        } finally {
                            LOG.fine("refreshAll - finished");  //NOI18N
                            synchronized (REFRESH_RP) {
                                refreshTask = null;
                            }
                        }
                    }
                });
            }
            taskToWaitFor = refreshTask;
            refreshTask.schedule(0);
            LOG.fine("refreshAll - scheduled");  //NOI18N
        }
        taskToWaitFor.waitFinished();
        LOG.fine("refreshAll - finished");  //NOI18N
    }

    /**
     * Registers listener so that it will receive
     * FileEvents from FileSystems providing instances
     * of FileObject convertible to java.io.File. 
     * @param fcl
     * @see #toFileObject
     * @since 7.7
     */
    public static void addFileChangeListener(FileChangeListener fcl) {
        FileSystem fs = getDiskFileSystem();
        if (fs == null) {fs = getDiskFileSystemFor(File.listRoots());}
        if (fs != null) {
            fs.addFileChangeListener(fcl);
        }
    }
    
    /**
     * Unregisters listener so that it will no longer receive
     * FileEvents from FileSystems providing instances
     * of FileObject convertible to java.io.File      
     * @param fcl
     * @see #toFileObject
     * @since 7.7
     */
    public static void removeFileChangeListener(FileChangeListener fcl) {
        FileSystem fs = getDiskFileSystem();
        if (fs == null) {fs = getDiskFileSystemFor(File.listRoots());}
        if (fs != null) {
            fs.removeFileChangeListener(fcl);
        }
    }

    /**
     * Adds a listener to changes in a 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.
     * 
* When given path represents a file ({@code path.isDirectory() == false}) *
    *
  • fileDataCreated event is fired when the file is created
  • *
  • fileDeleted event is fired when the file is deleted
  • *
  • fileChanged event is fired when the file is modified
  • *
  • fileRenamed event is fired when the file is renamed
  • *
  • fileAttributeChanged is fired when FileObject's attribute is changed
  • *
* When given path represents a folder ({@code path.isDirectory() == true}) *
    *
  • fileFolderCreated event is fired when the folder is created or a child folder created
  • *
  • fileDataCreated event is fired when a child file is created
  • *
  • fileDeleted event is fired when the folder is deleted or a child file/folder removed
  • *
  • fileChanged event is fired when a child file is modified
  • *
  • 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 * listen to any number of paths. Note that listeners are always held weakly * - if the listener is collected, it is quietly removed. * * @param listener FileChangeListener to listen to changes in path * @param path File path to listen to (even not existing) * * @see FileObject#addFileChangeListener * @since org.openide.filesystems 7.20 */ public static void addFileChangeListener(FileChangeListener listener, File path) { FileChangeImpl.addFileChangeListenerImpl(LOG, listener, path); } /** * Removes a listener to changes in a given path. * @param listener FileChangeListener to be removed * @param path File path in which listener was listening * @throws IllegalArgumentException if listener was not listening to given path * * @see FileObject#removeFileChangeListener * @since org.openide.filesystems 7.20 */ public static void removeFileChangeListener(FileChangeListener listener, File path) { FileChangeImpl.removeFileChangeListenerImpl(LOG, listener, path); } /** * 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 * @param path File path to listen to (even not existing) * * @since org.openide.filesystems 7.28 */ public static void addRecursiveListener(FileChangeListener listener, File path) { addRecursiveListener(listener, path, null, null); } /** 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 * @param path File path to listen to (even not existing) * @param stop an interface to interrupt the process of registering * the listener. If the call returns true, the process * of registering the listener is immediately interrupted * * @see FileObject#addRecursiveListener * @since org.openide.filesystems 7.37 */ public static void addRecursiveListener(FileChangeListener listener, File path, Callable stop) { 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. *
* When given path represents a file ({@code path.isDirectory() == false}), this * code behaves exactly like {@link #addFileChangeListener(org.openide.filesystems.FileChangeListener, java.io.File)}. * Usually the path shall represent a folder ({@code path.isDirectory() == true}) *
    *
  • fileFolderCreated event is fired when the folder is created or a child folder created
  • *
  • fileDataCreated event is fired when a child file is created
  • *
  • fileDeleted event is fired when the folder is deleted or a child file/folder removed
  • *
  • fileChanged event is fired when a child file is modified
  • *
  • 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
  • *
* The above events are delivered for changes in all subdirectories (recursively). * It is guaranteed that with each change at least one event is generated. * For example adding a folder does not notify about content of the folder, * hence one event is delivered. * * 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. * *
* As registering of the listener can take a long time, especially on deep * hierarchies, it is possible provide a callback stop. * This stop object is guaranteed to be called once per every folder on the * default (when masterfs module is included) implemention. If the call * to stop.call() returns true, then the registration of * 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 * 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 * the listener. If the call returns true, the process * of registering the listener is immediately interrupted. null * value disables this kind of callback. * @param recurseInto a file filter that may return false when * a folder should not be traversed into and external changes in it ignored. * null recurses into all subfolders * @since 7.61 */ public static void addRecursiveListener(FileChangeListener listener, File path, FileFilter recurseInto, Callable stop) { FileChangeImpl.addRecursiveListener(listener, path, recurseInto, stop); } /** * Removes a listener to changes under given path. * @param listener FileChangeListener to be removed * @param path File path in which listener was listening * @throws IllegalArgumentException if listener was not listening to given path * * @see FileObject#removeRecursiveListener * @since org.openide.filesystems 7.28 */ public static void removeRecursiveListener(FileChangeListener listener, File path) { FileChangeImpl.removeRecursiveListener(listener, path); } /** * 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 * is executed. *

* @param atomicCode code that is supposed to be run as atomic action. See {@link FileSystem#runAtomicAction} * @throws java.io.IOException * @since 7.5 */ @SuppressWarnings("deprecation") public static final void runAtomicAction(final AtomicAction atomicCode) throws IOException { Repository.getDefault().getDefaultFileSystem().runAtomicAction(atomicCode); } /** * 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 * is executed. *

* @param atomicCode code that is supposed to be run as atomic action. See {@link FileSystem#runAtomicAction} * @since 7.5 */ public static final void runAtomicAction(final Runnable atomicCode) { final AtomicAction action = new FileSystem.AtomicAction() { public void run() throws IOException { atomicCode.run(); } }; try { FileUtil.runAtomicAction(action); } 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 * 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 * @return FileObject for a folder * @throws java.io.IOException if the creation fails * @since 7.0 */ public static FileObject createFolder (final File folder) throws IOException { File existingFolder = folder; while(existingFolder != null && !existingFolder.isDirectory()) { existingFolder = existingFolder.getParentFile(); } if (existingFolder == null) { throw new IOException(folder.getAbsolutePath()); } FileObject retval = null; FileObject folderFo = FileUtil.toFileObject(existingFolder); assert folderFo != null : existingFolder.getAbsolutePath(); final String relativePath = getRelativePath(existingFolder, folder); try { retval = FileUtil.createFolder(folderFo,relativePath); } catch (IOException ex) { //thus retval = null; } //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; } /**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 * parent folders. * @param data data file to be created * @return FileObject for a data file * @throws java.io.IOException if the creation fails * @since 7.0 */ 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); } catch (IOException ex) { //thus retval = null; } //if refresh needed because of external changes if (retval == null || !retval.isValid()) { folderFo.getFileSystem().refresh(false); retval = FileUtil.createData(folderFo,relativePath); } assert retval != null; return retval; } private static String getRelativePath(final File dir, final File file) { Stack stack = new Stack(); File tempFile = file; while(tempFile != null && !tempFile.equals(dir)) { stack.push (tempFile.getName()); tempFile = tempFile.getParentFile(); } assert tempFile != null : file.getAbsolutePath() + "not found in " + dir.getAbsolutePath();//NOI18N StringBuilder retval = new StringBuilder(); while (!stack.isEmpty()) { retval.append(stack.pop()); 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. * @param is input stream * @param os output stream */ public static void copy(InputStream is, OutputStream os) throws IOException { final byte[] BUFFER = new byte[65536]; int len; for (;;) { len = is.read(BUFFER); if (len == -1) { return; } os.write(BUFFER, 0, len); } } /** Copies file to the selected folder. * This implementation simply copies the file by stream content. * @param source source file object * @param destFolder destination folder * @param newName file name (without extension) of destination file * @param newExt extension of destination file * @return the created file object in the destination folder * @exception IOException if destFolder is not a folder or does not exist; the destination file already exists; or * another critical error occurs during copying */ static FileObject copyFileImpl(FileObject source, FileObject destFolder, String newName, String newExt) throws IOException { FileObject dest = destFolder.createData(newName, newExt); FileLock lock = null; InputStream bufIn = null; OutputStream bufOut = null; try { lock = dest.lock(); bufIn = source.getInputStream(); if (dest instanceof AbstractFileObject) { /** prevents from firing fileChange*/ bufOut = ((AbstractFileObject) dest).getOutputStream(lock, false); } else { bufOut = dest.getOutputStream(lock); } copy(bufIn, bufOut); copyAttributes(source, dest); } finally { if (bufIn != null) { bufIn.close(); } if (bufOut != null) { bufOut.close(); } if (lock != null) { lock.releaseLock(); } } return dest; } // // public methods // /** Factory method that creates an empty implementation of a filesystem that * completely resides in a memory. *

To specify the MIME type of a data file without using a MIME resolver, * set the {@code mimeType} file attribute. *

Since 7.42, a {@link URLMapper} is available for files (and folders) * in memory filesystems. These URLs are valid only so long as the filesystem * has not been garbage-collected, so hold the filesystem (or a file in it) * strongly for as long as you expect the URLs to be in play. * @return a blank writable filesystem * @since 4.43 */ public static FileSystem createMemoryFileSystem() { return new MemoryFileSystem(); } /** Copies file to the selected folder. * This implementation simply copies the file by stream content. * @param source source file object * @param destFolder destination folder * @param newName file name (without extension) of destination file * @param newExt extension of destination file * @return the created file object in the destination folder * @exception IOException if destFolder is not a folder or does not exist; the destination file already exists; or * another critical error occurs during copying */ public static FileObject copyFile(FileObject source, FileObject destFolder, String newName, String newExt) throws IOException { return source.copy(destFolder, newName, newExt); } /** Copies file to the selected folder. * This implementation simply copies the file by stream content. * Uses the extension of the source file. * @param source source file object * @param destFolder destination folder * @param newName file name (without extension) of destination file * @return the created file object in the destination folder * @exception IOException if destFolder is not a folder or does not exist; the destination file already exists; or * another critical error occurs during copying */ public static FileObject copyFile(FileObject source, FileObject destFolder, String newName) throws IOException { return copyFile(source, destFolder, newName, source.getExt()); } /** Moves file to the selected folder. * This implementation uses a copy-and-delete mechanism, and automatically uses the necessary lock. * @param source source file object * @param destFolder destination folder * @param newName file name (without extension) of destination file * @return new file object * @exception IOException if either the {@link #copyFile copy} or {@link FileObject#delete delete} failed */ public static FileObject moveFile(FileObject source, FileObject destFolder, String newName) throws IOException { FileLock lock = null; try { lock = source.lock(); return source.move(lock, destFolder, newName, source.getExt()); } finally { if (lock != null) { lock.releaseLock(); } } } /** 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. * The name of the new folder can be * specified as a multi-component pathname whose components are separated * by File.separatorChar or "/" (forward slash). * * @param folder where the new folder will be placed in * @param name name of the new folder * @return the new folder * @exception IOException if the creation fails */ public static FileObject createFolder(FileObject folder, String name) throws IOException { String separators; if (File.separatorChar != '/') { separators = "/" + File.separatorChar; // NOI18N } else { separators = "/"; // NOI18N } StringTokenizer st = new StringTokenizer(name, separators); if(name.startsWith("//") || name.startsWith("\\\\")) { // NOI18N // if it is UNC absolute path, start with \\ComputerName\sharedFolder try { File root = new File("\\\\"+st.nextToken()+"\\"+st.nextToken()); // NOI18N folder = FileUtil.toFileObject(root); if (folder == null) { throw new IOException("Windows share "+root.getPath()+" does not exist"); // NOI18N } } catch (NoSuchElementException ex) { throw new IOException("Invalid Windows share "+name); // NOI18N } } while (st.hasMoreElements()) { name = st.nextToken(); if (name.length() > 0) { FileObject f = folder.getFileObject(name); if (f == null) { try { LOG.finest("createFolder - before create folder if not exists."); f = folder.createFolder(name); } catch (IOException ex) { // SyncFailedException or IOException when folder already exists // there might be unconsistency between the cache // and the disk, that is why folder.refresh(); // and try again f = folder.getFileObject(name); if (f == null) { // if still not found than we have to report the // exception throw ex; } } } folder = f; } } 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 * 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 ). * * @param folder to begin with creation at * @param name name of data file as a resource * @return the data file for given name * @exception IOException if the creation fails */ public static FileObject createData(FileObject folder, String name) throws IOException { Parameters.notNull("folder", folder); //NOI18N Parameters.notNull("name", name); //NOI18N String foldername; String dataname; String fname; String ext; int index = name.lastIndexOf('/'); FileObject data; // names with '/' on the end are not valid if (index >= name.length()) { throw new IOException("Wrong file name."); // NOI18N } // if name contains '/', create necessary folder first if (index != -1) { foldername = name.substring(0, index); dataname = name.substring(index + 1); folder = createFolder(folder, foldername); assert folder != null; } else { dataname = name; } // create data index = dataname.lastIndexOf('.'); if (index != -1) { fname = dataname.substring(0, index); ext = dataname.substring(index + 1); } else { fname = dataname; ext = ""; // NOI18N } data = folder.getFileObject(fname, ext); if (data == null) { try { data = folder.createData(fname, ext); assert data != null : "FileObject.createData cannot return null; called on " + folder + " + " + fname + " + " + ext; // #50802 } catch (SyncFailedException ex) { // there might be unconsistency between the cache // and the disk, that is why folder.refresh(); // and try again data = folder.getFileObject(fname, ext); if (data == null) { // if still not found than we have to report the // exception throw ex; } } } return data; } /** Finds appropriate java.io.File to FileObject if possible. * If not possible then null is returned. * This is the inverse operation of {@link #toFileObject}. * @param fo FileObject whose corresponding File will be looked for * @return java.io.File or null if no corresponding File exists. * @since 1.29 */ public static File toFile(FileObject fo) { File retVal = (File) fo.getAttribute("java.io.File"); // NOI18N; if (retVal == null) { try { if (fo.getFileSystem() instanceof JarFileSystem) { return null; } } catch (FileStateInvalidException ex) { return null; } URL fileURL = URLMapper.findURL(fo, URLMapper.INTERNAL); if (fileURL == null || !"file".equals(fileURL.getProtocol())) { //NOI18N fileURL = URLMapper.findURL(fo, URLMapper.EXTERNAL); } if ((fileURL != null) && "file".equals(fileURL.getProtocol())) { retVal = BaseUtilities.toFile(URI.create(fileURL.toExternalForm())); } } assert assertNormalized(retVal, BaseUtilities.isMac()); // #240180 return retVal; } /** * Converts a disk file to a matching file object. * This is the inverse operation of {@link #toFile}. *

* If you are running with {@code org.netbeans.modules.masterfs} enabled, * this method should never return null for a file which exists on disk. * For example, to make this method work in unit tests in an Ant-based module project, * right-click Unit Test Libraries, Add Unit Test Dependency, check Show Non-API Modules, select Master Filesystem. * (Also right-click the new Master Filesystem node, Edit, uncheck Include in Compile Classpath.) * To ensure masterfs (or some other module that can handle the conversion) * is present put following line into your module manifest: *

*
     * 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 * or there is no {@link URLMapper} available to convert it * @since 4.29 */ public static FileObject toFileObject(File file) { Parameters.notNull("file", file); //NOI18N // return null for UNC root if(file.getPath().equals("\\\\")) { return null; } boolean asserts = false; assert asserts = true; if (asserts) { File normFile = normalizeFile(file); if (!file.equals(normFile)) { 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)); } file = normFile; } FileObject retVal = null; try { URL url = BaseUtilities.toURI(file).toURL(); retVal = URLMapper.findFileObject(url); /*probably temporary piece of code to catch the cause of #46630*/ } catch (MalformedURLException e) { retVal = null; } if (retVal != null) { if (getDiskFileSystem() == null) { try { FileSystem fs = retVal.getFileSystem(); setDiskFileSystem(fs); } catch (FileStateInvalidException ex) { Exceptions.printStackTrace(ex); } } } 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. * @param file File whose corresponding FileObjects will be looked for. * The file has to be "normalized" otherwise IllegalArgumentException is thrown. * See {@link #normalizeFile} for how to do that. * @return corresponding FileObjects or empty array if no * corresponding FileObject exists. * @since 1.29 * @deprecated Use {@link #toFileObject} instead. */ @Deprecated public static FileObject[] fromFile(File file) { FileObject[] retVal; if (!file.equals(normalizeFile(file))) { throw new IllegalArgumentException( "Parameter file was not " + // NOI18N "normalized. Was " + toDebugString(file) + " instead of " + toDebugString(normalizeFile(file))); // NOI18N } try { URL url = (BaseUtilities.toURI(file).toURL()); retVal = URLMapper.findFileObjects(url); } catch (MalformedURLException e) { retVal = null; } return retVal; } /** Copies attributes from one file to another. * Note: several special attributes will not be copied, as they should * semantically be transient. These include attributes used by the * template wizard (but not the template attribute itself). * @param source source file object * @param dest destination file object * @exception IOException if the copying failed */ public static void copyAttributes(FileObject source, FileObject dest) throws IOException { Enumeration attrKeys = source.getAttributes(); while (attrKeys.hasMoreElements()) { String key = attrKeys.nextElement(); if (transientAttributes.contains(key)) { continue; } if (isTransient(source, key)) { continue; } 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 // by mistake in code. So it should happen only if you import some // settings from old version. if (value != null && !(value instanceof MultiFileObject.VoidValue)) { if (isRawValue.get() && value instanceof Method) { dest.setAttribute("methodvalue:" + key, value); // NOI18N } else if (isRawValue.get() && value instanceof Class) { dest.setAttribute("newvalue:" + key, value); // NOI18N } else { dest.setAttribute(key, value); } } } } static boolean isTransient(FileObject fo, String attrName) { return XMLMapAttr.ModifiedAttribute.isTransient(fo, attrName); } /** Extract jar file into folder represented by file object. If the JAR contains * files with name filesystem.attributes, it is assumed that these files * has been created by DefaultAttributes implementation and the content * of these files is treated as attributes and added to extracted files. *

META-INF/ directories are skipped over. * * @param fo file object of destination folder * @param is input stream of jar file * @exception IOException if the extraction fails * @deprecated Use of XML filesystem layers generally obsoletes this method. * For tests, use {@link org.openide.util.test.TestFileUtils#unpackZipFile}. */ @Deprecated public static void extractJar(final FileObject fo, final InputStream is) throws IOException { FileSystem fs = fo.getFileSystem(); fs.runAtomicAction( new FileSystem.AtomicAction() { public void run() throws IOException { extractJarImpl(fo, is); } } ); } /** Does the actual extraction of the Jar file. */ private static void extractJarImpl(FileObject fo, InputStream is) throws IOException { JarInputStream jis; JarEntry je; // files with extended attributes (name, DefaultAttributes.Table) HashMap attributes = new HashMap(7); jis = new JarInputStream(is); while ((je = jis.getNextJarEntry()) != null) { String name = je.getName(); if (name.toLowerCase().startsWith("meta-inf/")) { continue; // NOI18N } if (je.isDirectory()) { createFolder(fo, name); continue; } if (DefaultAttributes.acceptName(name)) { // file with extended attributes DefaultAttributes.Table table = DefaultAttributes.loadTable(jis, name); attributes.put(name, table); } else { // copy the file FileObject fd = createData(fo, name); FileLock lock = fd.lock(); try { OutputStream os = fd.getOutputStream(lock); try { copy(jis, os); } finally { os.close(); } } finally { lock.releaseLock(); } } } // // apply all extended attributes // Iterator it = attributes.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String fileName = (String) entry.getKey(); int last = fileName.lastIndexOf('/'); String dirName; if (last != -1) { dirName = fileName.substring(0, last + 1); } else { dirName = ""; // NOI18N } String prefix = fo.isRoot() ? dirName : (fo.getPath() + '/' + dirName); DefaultAttributes.Table t = (DefaultAttributes.Table) entry.getValue(); Iterator files = t.keySet().iterator(); while (files.hasNext()) { String orig = (String) files.next(); String fn = prefix + orig; FileObject obj = fo.getFileSystem().findResource(fn); if (obj == null) { continue; } Enumeration attrEnum = t.attrs(orig); while (attrEnum.hasMoreElements()) { // iterate thru all arguments String attrName = attrEnum.nextElement(); // Note: even transient attributes set here! Object value = t.getAttr(orig, attrName); if (value != null) { obj.setAttribute(attrName, value); } } } } } // extractJar /** Gets the extension of a specified file name. The extension is * everything after the last dot. * * @param fileName name of the file * @return extension of the file (or "" if it had none) */ public static String getExtension(String fileName) { int index = fileName.lastIndexOf("."); // NOI18N if (index == -1) { return ""; // NOI18N } else { return fileName.substring(index + 1); } } /** Finds an unused file name similar to that requested in the same folder. * The specified file name is used if that does not yet exist or is * {@link FileObject#isVirtual isVirtual}. * Otherwise, the first available name of the form basename_nnn.ext (counting from one) is used. * *

Caution: this method does not lock the parent folder * to prevent race conditions: i.e. it is possible (though unlikely) * that the resulting name will have been created by another thread * just as you were about to create the file yourself (if you are, * in fact, intending to create it just after this call). Since you * cannot currently lock a folder against child creation actions, * the safe approach is to use a loop in which a free name is * retrieved; an attempt is made to {@link FileObject#createData create} * that file; and upon an IOException during * creation, retry the loop up to a few times before giving up. * * @param folder parent folder * @param name preferred base name of file * @param ext extension to use (or null) * @return a free file name (without the extension) */ public static String findFreeFileName(FileObject folder, String name, String ext) { if (checkFreeName(folder, name, ext)) { return name; } for (int i = 1;; i++) { String destName = name + "_" + i; // NOI18N if (checkFreeName(folder, destName, ext)) { return destName; } } } /** Finds an unused folder name similar to that requested in the same parent folder. *

See caveat for findFreeFileName. * @see #findFreeFileName findFreeFileName * @param folder parent folder * @param name preferred folder name * @return a free folder name */ public static String findFreeFolderName(FileObject folder, String name) { if (checkFreeName(folder, name, null)) { return name; } for (int i = 1;; i++) { String destName = name + "_" + i; // NOI18N if (checkFreeName(folder, destName, null)) { return destName; } } } /** * Gets a relative resource path between folder and fo. * @param folder root of filesystem or any other folder in folders hierarchy * @param fo arbitrary FileObject in folder's tree (including folder itself) * @return relative path between folder and fo. The returned path never * starts with a '/'. It never ends with a '/'. Specifically, if * folder==fo, returns "". Returns null if fo is not in * folder's tree. * @see #isParentOf * @since 4.16 */ public static String getRelativePath(FileObject folder, FileObject fo) { if (!isParentOf(folder, fo) && (folder != fo)) { return null; } String result = fo.getPath().substring(folder.getPath().length()); if (result.startsWith("/") && !result.startsWith("//")) { result = result.substring(1); } return result; } /** Test if given name is free in given folder. * @param fo folder to check in * @param name name of the file or folder to check * @param ext extension of the file (null for folders) * @return true, if such name does not exists */ private static boolean checkFreeName(FileObject fo, String name, String ext) { if ((BaseUtilities.isWindows() || (BaseUtilities.getOperatingSystem() == BaseUtilities.OS_OS2)) || BaseUtilities.isMac()) { // case-insensitive, do some special check Enumeration en = fo.getChildren(false); while (en.hasMoreElements()) { fo = en.nextElement(); String n = fo.getName(); String e = fo.getExt(); // different names => check others if (!n.equalsIgnoreCase(name)) { continue; } // same name + without extension => no if (((ext == null) || (ext.trim().length() == 0)) && ((e == null) || (e.trim().length() == 0))) { return fo.isVirtual(); } // one of there is witout extension => check next if ((ext == null) || (e == null)) { continue; } if (ext.equalsIgnoreCase(e)) { // same name + same extension => no return fo.isVirtual(); } } // no of the files has similar name and extension return true; } else { if (ext == null) { fo = fo.getFileObject(name); if (fo == null) { return true; } return fo.isVirtual(); } else { fo = fo.getFileObject(name, ext); if (fo == null) { return true; } return fo.isVirtual(); } } } // note: "sister" is preferred in English, please don't ask me why --jglick // NOI18N /** Finds brother file with same base name but different extension. * @param fo the file to find the brother for or null * @param ext extension for the brother file * @return a brother file (with the requested extension and the same parent folder as the original) or * null if the brother file does not exist or the original file was null */ public static FileObject findBrother(FileObject fo, String ext) { if (fo == null) { return null; } FileObject parent = fo.getParent(); if (parent == null) { return null; } return parent.getFileObject(fo.getName(), ext); } /** Obtain MIME type for a well-known extension. * If there is a case-sensitive match, that is used, else will fall back * to a case-insensitive match. * @param ext the extension: "jar", "zip", etc. * @return the MIME type for the extension, or null if the extension is unrecognized * @deprecated use {@link #getMIMEType(FileObject)} or {@link #getMIMEType(FileObject, String[])} * as MIME cannot be generally detected by file object extension. */ @Deprecated public static String getMIMEType(String ext) { assert false : "FileUtil.getMIMEType(String extension) is deprecated. Please, use FileUtil.getMIMEType(FileObject)."; //NOI18N if (ext.toLowerCase().equals("xml")) { //NOI18N return "text/xml"; // NOI18N } return null; } /** Resolves MIME type. Registered resolvers are invoked and used to achieve this goal. * Resolvers must subclass MIMEResolver. * @param fo whose MIME type should be recognized * @return the MIME type for the FileObject, or {@code null} if the FileObject is unrecognized. * It may return {@code content/unknown} instead of {@code null}. */ public static String getMIMEType(FileObject fo) { return MIMESupport.findMIMEType(fo); } /** Resolves MIME type. Registered resolvers are invoked and used to achieve this goal. * Resolvers must subclass MIMEResolver. By default it is possible for the * method to return {@code content/unknown} instead of {@code null} - if * you want to avoid such behavior, include null in the * list of requested withinMIMETypes - in such case the * return value is guaranteed to be one of the values in withinMIMETypes * or null. *

* Example: Check if some file is Java source file or text file: *

* * FileUtil.getMIMEType(fo, null, "text/x-java", "text/plain") != null * * @param fo whose MIME type should be recognized * @param withinMIMETypes an array of MIME types. Only resolvers whose * {@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 * the FileObject is unrecognized. * @since 7.13 */ public static String getMIMEType(FileObject fo, String... withinMIMETypes) { Parameters.notNull("withinMIMETypes", withinMIMETypes); //NOI18N String res = MIMESupport.findMIMEType(fo, withinMIMETypes); if (res == null) { return null; } boolean foundNull = false; for(String t : withinMIMETypes) { if (t == null) { foundNull = true; continue; } if (res.equals(t)) { return t; } } return foundNull ? null : res; } /** 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 * relevant (for example {@literal *.C} for C++) but if you register * a lowercase extension it will by default apply to uppercase extensions * too on Windows. * @param extension the file extension to be registered * @param mimeType the MIME type to be registered for the extension or {@code null} to deregister * @see #getMIMEType(FileObject) * @see #getMIMETypeExtensions(String) */ public static void setMIMEType(String extension, String mimeType) { Parameters.notEmpty("extension", extension); //NOI18N final Map> mimeToExtensions = new HashMap>(); FileObject userDefinedResolverFO = MIMEResolverImpl.getUserDefinedResolver(); if (userDefinedResolverFO != null) { // add all previous content mimeToExtensions.putAll(MIMEResolverImpl.getMIMEToExtensions(userDefinedResolverFO)); // exclude extension possibly registered for other MIME types for (Set extensions : mimeToExtensions.values()) { extensions.remove(extension); } } if (mimeType != null) { // add specified extension to our structure Set previousExtensions = mimeToExtensions.get(mimeType); if (previousExtensions != null) { previousExtensions.add(extension); } else { mimeToExtensions.put(mimeType, Collections.singleton(extension)); } } if (MIMEResolverImpl.storeUserDefinedResolver(mimeToExtensions)) { MIMESupport.resetCache(); } MIMESupport.freeCaches(); } /** Returns list of file extensions associated with specified MIME type. In * other words files with those extensions are recognized as specified MIME type * in NetBeans' filesystem. It never returns {@code null}. * @param mimeType the MIME type (e.g. image/gif) * @return list of file extensions associated with specified MIME type, never {@code null} * @see #setMIMEType(String, String) * @since org.openide.filesystems 7.18 */ public static List getMIMETypeExtensions(String mimeType) { Parameters.notEmpty("mimeType", mimeType); //NOI18N HashMap extensionToMime = new HashMap(); for (FileObject mimeResolverFO : MIMEResolverImpl.getOrderedResolvers()) { Map> mimeToExtensions = MIMEResolverImpl.getMIMEToExtensions(mimeResolverFO); for (Map.Entry> entry : mimeToExtensions.entrySet()) { String mimeKey = entry.getKey(); Set extensions = entry.getValue(); for (String extension : extensions) { extensionToMime.put(extension, mimeKey); } } } List registeredExtensions = new ArrayList(); for (Map.Entry entry : extensionToMime.entrySet()) { if (entry.getValue().equals(mimeType)) { registeredExtensions.add(entry.getKey()); } } return registeredExtensions; } /** * @deprecated No longer used. */ @Deprecated public static URLStreamHandler nbfsURLStreamHandler() { return new FileURL.Handler(); } /** Recursively checks whether the file is underneath the folder. It checks whether * the file and folder are located on the same filesystem, in such case it checks the * parent FileObject of the file recursively until the folder is found * or the root of the filesystem is reached. *

Warning: this method will return false in the case that * folder == fo. * @param folder the root of folders hierarchy to search in * @param fo the file to search for * @return true, if fo lies somewhere underneath the folder, * false otherwise * @since 3.16 */ public static boolean isParentOf(FileObject folder, FileObject fo) { Parameters.notNull("folder", folder); //NOI18N Parameters.notNull("fo", fo); //NOI18N if (folder.isData()) { return false; } try { if (folder.getFileSystem() != fo.getFileSystem()) { return false; } } catch (FileStateInvalidException e) { return false; } FileObject parent = fo.getParent(); while (parent != null) { if (parent.equals(folder)) { return true; } parent = parent.getParent(); } return false; } /** * Check whether some FileObject is a recursive symbolic link. * * @param fo FileObject to check. * * @return True if the FileObject represents a symbolic link referring to a * directory that is parent of this FileObject, false otherwise. * @throws java.io.IOException If some I/O problem occurs. * @since openide.filesystem/9.4 */ public static boolean isRecursiveSymbolicLink(FileObject fo) throws IOException { if (!fo.isFolder()) { return false; } else if (fo.isSymbolicLink()) { FileObject parent = fo.getParent(); if (parent == null) { return false; } FileObject target = fo.readSymbolicLink(); if (target != null) { FileObject realParent = parent.getCanonicalFileObject(); FileObject realTarget = target.getCanonicalFileObject(); return realParent != null && realTarget != null && (realTarget.equals(realParent) || FileUtil.isParentOf(realTarget, realParent)); } else { return false; } } else { return false; } } /** Creates a weak implementation of FileChangeListener. * * @param l the listener to delegate to * @param source the source that the listener should detach from when * listener l is freed, can be null * @return a FileChangeListener delegating to l. * @since 4.10 */ public static FileChangeListener weakFileChangeListener(FileChangeListener l, Object source) { return WeakListeners.create(FileChangeListener.class, l, source); } /** Creates a weak implementation of FileStatusListener. * * @param l the listener to delegate to * @param source the source that the listener should detach from when * listener l is freed, can be null * @return a FileChangeListener delegating to l. * @since 4.10 */ public static FileStatusListener weakFileStatusListener(FileStatusListener l, Object source) { return WeakListeners.create(FileStatusListener.class, l, source); } /** * Get an appropriate display name for a file object. * If the file corresponds to a path on disk, this will be the disk path. * Otherwise the name will mention the filesystem name or archive name in case * the file comes from archive and relative path. Relative path will be mentioned * just in case that passed FileObject isn't root {@link FileObject#isRoot}. * * @param fo a file object * @return a display name indicating where the file is * @since 4.39 */ public static String getFileDisplayName(FileObject fo) { String displayName = null; File f = FileUtil.toFile(fo); if (f != null) { displayName = f.getAbsolutePath(); } else { FileObject archiveFile = FileUtil.getArchiveFile(fo); if (archiveFile != null) { displayName = getArchiveDisplayName(fo, archiveFile); } } if (displayName == null) { try { if (fo.isRoot()) { displayName = fo.getFileSystem().getDisplayName(); } else { displayName = NbBundle.getMessage( FileUtil.class, "LBL_file_in_filesystem", fo.getPath(), fo.getFileSystem().getDisplayName() ); } } catch (FileStateInvalidException e) { // Not relevant now, just use the simple path. displayName = fo.getPath(); } } return displayName; } private static String getArchiveDisplayName(FileObject fo, FileObject archiveFile) { String displayName = null; File f = FileUtil.toFile(archiveFile); if (f != null) { String archivDisplayName = f.getAbsolutePath(); if (fo.isRoot()) { displayName = archivDisplayName; } else { String entryPath = fo.getPath(); displayName = NbBundle.getMessage( FileUtil.class, "LBL_file_in_filesystem", entryPath, archivDisplayName ); } } return displayName; } /** * See {@link #normalizeFile} for details * @param path file path to normalize * @return normalized file path * @since 7.42 */ public static String normalizePath(final String path) { Map normalizedPaths = getNormalizedFilesMap(); String normalized = normalizedPaths.get(path); if (normalized == null) { File ret = normalizeFileImpl(new File(path)); normalized = ret.getPath(); normalizedPaths.put(path, normalized); } return normalized; } /** * Normalize a file path to a clean form. * This method may for example make sure that the returned file uses * the natural case on Windows; that old Windows 8.3 filenames are changed to the long form; * that relative paths are changed to be * absolute; that . and .. sequences are removed; etc. * Unlike {@link File#getCanonicalFile} this method will not traverse symbolic links on Unix. *

This method involves some overhead and should not be called frivolously. * Generally it should be called on incoming pathnames that are gotten from user input * (including filechoosers), configuration files, Ant properties, etc. Internal * calculations should not need to renormalize paths since {@link File#listFiles}, * {@link File#getParentFile}, etc. will not produce abnormal variants. * @param file file to normalize * @return normalized file * @since 4.48 */ public static File normalizeFile(final File file) { File ret = normalizeFileCached(file); 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.equals(unnormalized) ) { normalized = null; } File ret; if (normalized == null) { ret = normalizeFileImpl(file); assert !ret.getName().equals(".") : "Original file " + file + " normalized: " + ret; normalizedPaths.put(unnormalized, ret.getPath()); } else if (normalized.equals(unnormalized)) { ret = file; } else { ret = new File(normalized); } return ret; } private static File normalizeFileImpl(File file) { // XXX should use NIO in JDK 7; see #6358641 Parameters.notNull("file", file); //NOI18N File retFile; LOG.log(Level.FINE, "FileUtil.normalizeFile for {0}", file); // NOI18N long now = System.currentTimeMillis(); if ((BaseUtilities.isWindows() || (BaseUtilities.getOperatingSystem() == BaseUtilities.OS_OS2))) { retFile = normalizeFileOnWindows(file); } else if (BaseUtilities.isMac()) { retFile = normalizeFileOnMac(file); } else { retFile = normalizeFileOnUnixAlike(file); } File ret = (file.getPath().equals(retFile.getPath())) ? file : retFile; long took = System.currentTimeMillis() - now; if (took > 500) { LOG.log(Level.WARNING, "FileUtil.normalizeFile({0}) took {1} ms. Result is {2}", new Object[]{file, took, ret}); } return ret; } private static File normalizeFileOnUnixAlike(File file) { // On Unix, do not want to traverse symlinks. // URI.normalize removes ../ and ./ sequences nicely. file = BaseUtilities.toFile(BaseUtilities.toURI(file).normalize()).getAbsoluteFile(); while (file.getAbsolutePath().startsWith("/../")) { // NOI18N file = new File(file.getAbsolutePath().substring(3)); } if (file.getAbsolutePath().equals("/..")) { // NOI18N // Special treatment. file = new File("/"); // NOI18N } return file; } private static File normalizeFileOnMac(final File file) { File retVal = file; File absoluteFile = BaseUtilities.toFile(BaseUtilities.toURI(file).normalize()); String absolutePath = absoluteFile.getAbsolutePath(); if (absolutePath.equals("/..")) { // NOI18N // Special treatment. absoluteFile = new File(absolutePath = "/"); // NOI18N } try { // URI.normalize removes ../ and ./ sequences nicely. File canonicalFile = file.getCanonicalFile(); boolean isSymLink = !canonicalFile.getAbsolutePath().equalsIgnoreCase(absolutePath); if (isSymLink) { retVal = normalizeSymLinkOnMac(absoluteFile); } else { retVal = canonicalFile; } } catch (IOException ioe) { LOG.log(Level.FINE, "Normalization failed on file " + file, ioe); // OK, so at least try to absolutize the path retVal = absoluteFile.getAbsoluteFile(); } return retVal; } /** * @param file is expected to be already absolute with removed ../ and ./ */ private static File normalizeSymLinkOnMac(final File file) throws IOException { File retVal = File.listRoots()[0]; File pureCanonicalFile = retVal; final String pattern = File.separator + ".." + File.separator; //NOI18N final String fileName; { // strips insufficient non-".." segments preceding them String tmpFileName = file.getAbsolutePath(); int index = tmpFileName.lastIndexOf(pattern); if (index > -1) { tmpFileName = tmpFileName.substring(index + pattern.length()); //Remove starting {/../}* } fileName = tmpFileName; } /*normalized step after step*/ StringTokenizer fileSegments = new StringTokenizer(fileName, File.separator); while (fileSegments.hasMoreTokens()) { File absolutelyEndingFile = new File(pureCanonicalFile, fileSegments.nextToken()); pureCanonicalFile = absolutelyEndingFile.getCanonicalFile(); boolean isSymLink = !pureCanonicalFile.getAbsolutePath().equalsIgnoreCase( absolutelyEndingFile.getAbsolutePath() ); if (isSymLink) { retVal = new File(retVal, absolutelyEndingFile.getName()); } else { retVal = new File(retVal, pureCanonicalFile.getName()); } } return retVal; } 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(); } } 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); } if (path.endsWith(".")) { path = path.substring(0, path.length() - 1); retVal = new File(path); } if (path.length() > 3 && path.endsWith("\\")) { path = path.substring(0, path.length() - 1); retVal = new File(path); } } // #135547 - on Windows Vista map "Documents and Settings\\My Documents" to "Users\\Documents" if((BaseUtilities.getOperatingSystem() & BaseUtilities.OS_WINVISTA) != 0) { if(retVal == null) { retVal = file.getAbsoluteFile(); } String absolutePath = retVal.getAbsolutePath(); if(absolutePath.contains(":\\Documents and Settings")) { //NOI18N absolutePath = absolutePath.replaceFirst("Documents and Settings", "Users"); //NOI18N absolutePath = absolutePath.replaceFirst("My Documents", "Documents"); //NOI18N absolutePath = absolutePath.replaceFirst("My Pictures", "Pictures"); //NOI18N absolutePath = absolutePath.replaceFirst("My Music", "Music"); //NOI18N retVal = new File(absolutePath); } } return (retVal != null) ? retVal : file.getAbsoluteFile(); } private static Reference> normalizedRef = new SoftReference>(new ConcurrentHashMap()); private static Map getNormalizedFilesMap() { Map map = normalizedRef.get(); if (map == null) { synchronized (FileUtil.class) { map = normalizedRef.get(); if (map == null) { map = new ConcurrentHashMap(); normalizedRef = new SoftReference>(map); } } } return map; } static void freeCaches() { normalizedRef.clear(); } /** * Returns a FileObject representing the root folder of an archive. * Clients may need to first call {@link #isArchiveFile(FileObject)} to determine * if the file object refers to an archive file. * @param fo a java archive file, by default ZIP and JAR are supported * @return a virtual archive root folder, or null if the file is not actually an archive * @since 4.48 */ public static FileObject getArchiveRoot(FileObject fo) { for (ArchiveRootProvider provider : getArchiveRootProviders()) { if (provider.isArchiveFile(fo, false)) { final FileObject root = provider.getArchiveRoot(fo); if (root != null) { return root; } } } return null; } /** * Returns a URL representing the root of an archive. * Clients may need to first call {@link #isArchiveFile(URL)} to determine if the URL * refers to an archive file. * @param url of a java archive file, by default ZIP and JAR are supported * @return the archive (eg. jar) protocol URL of the root of the archive. * @since 4.48 */ public static URL getArchiveRoot(URL url) { for (ArchiveRootProvider provider : getArchiveRootProviders()) { if (provider.isArchiveFile(url, false)) { final URL root = provider.getArchiveRoot(url); if (root != null) { return root; } } } //For compatibility reason never return null but return the jar URL. return getArchiveRootProviders().iterator().next().getArchiveRoot(url); } /** * Returns a FileObject representing an archive file containing the * FileObject given by the parameter. * Remember that any path within the archive is discarded * so you may need to check for non-root entries. * @param fo a file in an archive filesystem * @return the file corresponding to the archive itself, * or null if fo is not an archive entry * @since 4.48 */ public static FileObject getArchiveFile(FileObject fo) { Parameters.notNull("fo", fo); //NOI18N for (ArchiveRootProvider provider : getArchiveRootProviders()) { if (provider.isArchiveArtifact(fo)) { final FileObject file = provider.getArchiveFile(fo); if (file != null) { return file; } } } return null; } /** * Returns the URL of the archive file containing the file * referred to by an archive (eg. jar) protocol URL. * Remember that any path within the archive is discarded * so you may need to check for non-root entries. * @param url a URL * @return the embedded archive URL, or null if the URL is not an * archive protocol URL containing !/ * @since 4.48 */ public static URL getArchiveFile(URL url) { for (ArchiveRootProvider provider : getArchiveRootProviders()) { if (provider.isArchiveArtifact(url)) { final URL file = provider.getArchiveFile(url); if (file != null) { return file; } } } return null; } /** * Tests if a file represents a java archive. * By default the JAR or ZIP archives are supported. * @param fo the file to be tested * @return true if the file looks like a java archive * @since 4.48 */ public static boolean isArchiveFile(FileObject fo) { Parameters.notNull("fileObject", fo); //NOI18N for (ArchiveRootProvider provider : getArchiveRootProviders()) { if (provider.isArchiveFile(fo, true)) { return true; } } return false; } /** * Tests if a URL represents a java archive. * By default the JAR or ZIP archives are supported. * If there is no such file object, the test is done by heuristic: any URL with an extension is * treated as an archive. * @param url a URL to a file * @return true if the URL seems to represent a java archive * @since 4.48 */ public static boolean isArchiveFile(URL url) { Parameters.notNull("url", url); //NOI18N return isArchiveFileImpl(url, true); } /** * Tests if an file is inside an archive. * @param fo the file to be tested * @return true if the file is inside an archive * @since 9.10 */ public static boolean isArchiveArtifact(FileObject fo) { Parameters.notNull("fileObject", fo); //NOI18N for (ArchiveRootProvider provider : getArchiveRootProviders()) { if (provider.isArchiveArtifact(fo)) { return true; } } return false; } /** * Tests if an {@link URL} denotes a file inside an archive. * @param url the url to be tested * @return true if the url points inside an archive * @since 9.10 */ public static boolean isArchiveArtifact(URL url) { Parameters.notNull("url", url); //NOI18N for (ArchiveRootProvider provider : getArchiveRootProviders()) { if (provider.isArchiveArtifact(url)) { return true; } } return false; } /** * Convert a file such as would be shown in a classpath entry into a proper folder URL. * If the file looks to represent a directory, a file URL will be created. * If it looks to represent a ZIP archive, a jar URL will be created. * @param entry a file or directory name * @return an appropriate classpath URL which will always end in a slash (/), * or null for an existing file which does not look like a valid archive * @since org.openide.filesystems 7.8 */ public static URL urlForArchiveOrDir(File entry) { try { boolean wasDir; boolean isDir; URL u; do { wasDir = entry.isDirectory(); LOG.finest("urlForArchiveOrDir:toURI:entry"); //NOI18N u = BaseUtilities.toURI(entry).toURL(); isDir = entry.isDirectory(); } while (wasDir ^ isDir); if (isArchiveFileImpl(u, false)) { return getArchiveRoot(u); } else if (isDir) { assert u.toExternalForm().endsWith("/"); //NOI18N return u; } else if (!entry.exists()) { if (!u.toString().endsWith("/")) { //NOI18N u = new URL(u + "/"); // NOI18N } return u; } else { return null; } } catch (MalformedURLException x) { assert false : x; return null; } } /** * Convert a classpath-type URL to a corresponding file. * If it is a jar URL representing the root folder of a local disk archive, * that archive file will be returned. * If it is a file URL representing a local disk folder, * that folder will be returned. * @param entry a classpath entry or similar URL * @return a corresponding file, or null for e.g. a network URL or non-root JAR folder entry * @since org.openide.filesystems 7.8 */ public static File archiveOrDirForURL(URL entry) { final String u = entry.toString(); if (isArchiveArtifact(entry)) { entry = getArchiveFile(entry); try { return u.endsWith("!/") && entry != null && "file".equals(entry.getProtocol()) ? //NOI18N BaseUtilities.toFile(entry.toURI()): null; } catch (URISyntaxException e) { LOG.log( Level.WARNING, "Invalid URI: {0} ({1})", //NOI18N new Object[]{ entry, e.getMessage() }); return null; } } else if (u.startsWith("file:")) { // NOI18N return BaseUtilities.toFile(URI.create(u)); } else { return null; } } /** * Make sure that a JFileChooser does not traverse symlinks on Unix. * @param chooser a file chooser * @param currentDirectory if not null, a file to set as the current directory * using {@link javax.swing.JFileChooser#setCurrentDirectory} without canonicalizing * @see Issue #46459 * @see JRE bug #4906607 * @since org.openide/1 4.42 * @deprecated Just use {@link javax.swing.JFileChooser#setCurrentDirectory}. JDK 6 does not have this bug. */ @Deprecated public static void preventFileChooserSymlinkTraversal(javax.swing.JFileChooser chooser, File currentDirectory) { chooser.setCurrentDirectory(currentDirectory); } /** * Sorts some sibling file objects. *

Normally this is done by looking for numeric file attributes named position * on the children; children with a lower position number are placed first. * Now-deprecated relative ordering attributes of the form earlier/later may * also be used; if the above attribute has a boolean value of true, * then the file named earlier will be sorted somewhere (not necessarily directly) * before the file named later. Numeric and relative attributes may also be mixed.

*

The sort is stable at least to the extent that if there is no ordering information * whatsoever, the returned list will be in the same order as the incoming collection.

* @param children zero or more files (or folders); must all have the same {@link FileObject#getParent} * @param logWarnings true to log warnings about relative ordering attributes or other semantic problems, false to keep quiet * @return a sorted list of the same children * @throws IllegalArgumentException in case there are duplicates, or nulls, or the files do not have a common parent * @since org.openide.filesystems 7.2 * @see #setOrder * @see Specification */ public static List getOrder(Collection children, boolean logWarnings) throws IllegalArgumentException { return Ordering.getOrder(children, logWarnings); } /** * Imposes an order on some sibling file objects. * After this call, if no other changes have intervened, * {@link #getOrder} on these files should return a list in the same order. * Beyond the fact that this call may manipulate the position attributes * of files in the folder, and may delete deprecated relative ordering attributes on the folder, * the exact means of setting the order is unspecified. * @param children a list of zero or more files (or folders); must all have the same {@link FileObject#getParent} * @throws IllegalArgumentException in case there are duplicates, or nulls, or the files do not have a common parent * @throws IOException if new file attributes to order the children cannot be written out * @since org.openide.filesystems 7.2 */ public static void setOrder(List children) throws IllegalArgumentException, IOException { Ordering.setOrder(children); } /** * Checks whether a change in a given file attribute would affect the result of {@link #getOrder}. * @param event an attribute change event * @return true if the attribute in question might affect the order of some folder * @since org.openide.filesystems 7.2 */ public static boolean affectsOrder(FileAttributeEvent event) { return Ordering.affectsOrder(event); } /** * Returns {@code FileObject} from the NetBeans default (system, configuration) * filesystem or {@code null} if does not exist. * If you wish to create the file/folder when it does not already exist, * start with {@link #getConfigRoot} and use {@link #createData(FileObject, String)} * or {@link #createFolder(FileObject, String)} methods. *

* In environment with multiple contextual Lookups, the method may return different FileObject depending * on what Lookup serves the executing thread. If the system-wide (user-independent) configuration * is required instead, {@link #getSystemConfigFile} should be called instead. If an service instance is created based * on the configuration, it is important to decide whether the service instance should live for each context * independently (possibly with some private means of communication between instances/users) or all users * should share the same instance. In the later case, {@link #getSystemConfigFile} must be used. * * @param path the path from the root of the NetBeans default (system, configuration) * filesystem delimited by '/' or empty string to get root folder. * @throws NullPointerException if the path is {@code null} * @return a {@code FileObject} for given path in the NetBeans default (system, configuration) * filesystem or {@code null} if does not exist * * @since org.openide.filesystems 7.19 * @since 9.5 support for multiuser environment */ @SuppressWarnings("deprecation") public static FileObject getConfigFile(String path) { Parameters.notNull("path", path); //NOI18N Repository repo = Repository.getLocalRepository(); return (repo != null ? repo : Repository.getDefault()).getDefaultFileSystem().findResource(path); } /** * Returns {@code FileObject} from the default filesystem, or {@code null} if the file does not exist. * Unlike {@link #getConfigFile}, this call returns a FileObject from the system-wide configuration. * Because default/config filesystem is used both for configuration and services, Lookup or service providers * should use this method in preference to {@link #getConfigFile} to produce singleton services even * in multiple context environment. *

* With the default Lookup implementation, behaviour of {@code getSystemConfigFile} and {@link #getConfigFile} * is identical. * * @param path the path from the root of the NetBeans default (system, configuration) * filesystem delimited by '/' or empty string to get root folder. * @throws NullPointerException if the path is {@code null} * @return a {@code FileObject} for given path in the NetBeans default (system, configuration) * filesystem or {@code null} if does not exist * @since 9.5 */ @SuppressWarnings("deprecation") public static FileObject getSystemConfigFile(String path) { 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.: *

     * Actions/Edit/org-openide-actions-CopyAction.instance
     * Services/Browsers/swing-browser.settings
     * 
*

* In multi-user setup, this method returns instance specific for the executing user. * Important: it returns user-specific instance even though the object is configured in * a XML layer, or system-wide configuration; still, the instance will be tied to the user-specific * file as served by {@link #getConfigFile}. * * @param path 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 9.5 support for multi-user environment */ public static T getConfigObject(String path, Class type) { FileObject fo = getConfigFile(path); if (fo == null || fo.isFolder()) { return null; } return NamedServicesProvider.getConfigObject(path, type); } /** * Finds a config object under the given path, in system-wide configuration. * In the default implementation, this method works just as {@link #getConfigObject}. In * multi-user setups, this method should return an instance shared between * potential users; in a sense, it works as {@link #getConfigObject} prior version 9.5 * * @param path path to .instance or .settings file * @param type the requested type for given object * @return either null or instance of requrested type * @since 9.5 */ public static T getSystemConfigObject(String path, Class type) { FileObject fo = getSystemConfigFile(path); if (fo == null || fo.isFolder()) { return null; } return NamedServicesProvider.getConfigObject(path, type); } /** * Returns the root of the NetBeans default (system, configuration) * filesystem. It returns configuration root using the current Repository, in the case * that multiple Repository instances are created to support multiple execution contexts * in the same JVM. * * @return a {@code FileObject} for the root of the NetBeans default (system, configuration) * filesystem * @since org.openide.filesystems 7.19 * @since 9.5 support for multiple local contexts */ public static FileObject getConfigRoot() { return getConfigFile(""); //NOI18N } /** * Returns the root of the NetBeans default (system, configuration) * filesystem. Unlike {@link #getConfigRoot}, this method always provides the * system-wide configuration root. * * @return a {@code FileObject} for the root of the NetBeans default (system, configuration) * filesystem * @since 9.5 */ public static FileObject getSystemConfigRoot() { return getSystemConfigFile(""); } private static File wrapFileNoCanonicalize(File f) { if (f instanceof NonCanonicalizingFile) { return f; } else if (f != null) { return new NonCanonicalizingFile(f); } else { return null; } } private static File[] wrapFilesNoCanonicalize(File[] fs) { if (fs != null) { for (int i = 0; i < fs.length; i++) { fs[i] = wrapFileNoCanonicalize(fs[i]); } } return fs; } private static final class NonCanonicalizingFile extends File { public NonCanonicalizingFile(File orig) { this(orig.getPath()); } private NonCanonicalizingFile(String path) { super(path); } private NonCanonicalizingFile(URI uri) { super(uri); } @Override public File getCanonicalFile() throws IOException { return wrapFileNoCanonicalize(normalizeFile(super.getAbsoluteFile())); } @Override public String getCanonicalPath() throws IOException { return normalizeFile(super.getAbsoluteFile()).getAbsolutePath(); } @Override public File getParentFile() { return wrapFileNoCanonicalize(super.getParentFile()); } @Override public File getAbsoluteFile() { return wrapFileNoCanonicalize(super.getAbsoluteFile()); } @Override public File[] listFiles() { return wrapFilesNoCanonicalize(super.listFiles()); } @Override public File[] listFiles(FileFilter filter) { return wrapFilesNoCanonicalize(super.listFiles(filter)); } @Override public File[] listFiles(FilenameFilter filter) { return wrapFilesNoCanonicalize(super.listFiles(filter)); } } private static FileSystem getDiskFileSystem() { synchronized (FileUtil.class) { return diskFileSystem; } } private static void setDiskFileSystem(FileSystem fs) { Object o = fs.getRoot().getAttribute("SupportsRefreshForNoPublicAPI"); if (o instanceof Boolean && ((Boolean) o).booleanValue()) { synchronized (FileUtil.class) { diskFileSystem = fs; } } } private static boolean isArchiveFileImpl(final URL url, final boolean strict) { for (ArchiveRootProvider provider : getArchiveRootProviders()) { if (provider.isArchiveFile(url, strict)) { return true; } } return false; } private static Iterable getArchiveRootProviders() { Lookup.Result res = archiveRootProviders.get(); if (res == null) { res = new ProxyLookup( Lookups.singleton(new JarArchiveRootProvider()), Lookup.getDefault()).lookupResult(ArchiveRootProvider.class); if (!archiveRootProviders.compareAndSet(null, res)) { res = archiveRootProviders.get(); } } return res.allInstances(); } private static final AtomicReference> archiveRootProviders = new AtomicReference<>(); }