This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 252644
Collapse All | Expand All

(-)a/openide.filesystems/apichanges.xml (+17 lines)
Lines 49-54 Link Here
49
        <apidef name="filesystems">Filesystems API</apidef>
49
        <apidef name="filesystems">Filesystems API</apidef>
50
    </apidefs>
50
    </apidefs>
51
    <changes>
51
    <changes>
52
        <change id="ArchiveRootProvider">
53
            <api name="filesystems"/>
54
            <summary>Pluggable archive files support for FileUtil</summary>
55
            <version major="9" minor="6"/>
56
            <date year="2015" month="5" day="28"/>
57
            <author login="tzezula"/>
58
            <compatibility addition="yes" semantic="compatible" binary="compatible"/>
59
            <description>
60
                To support JDK 9 image file as an archive the FileUtil's method
61
                <code>getArchiveFile</code>,<code>getArchiveRoot</code> and <code>isArchiveFile</code>
62
                are pluggable using a new SPI <code>ArchiveFileProvider</code>.
63
                In addition to these methods a new method <code>isArchiveArtifact</code> was added.
64
                This method can be used if given <code>URL</code> points into an archive.
65
            </description>
66
            <class name="FileUtil" package="org.openide.filesystems"/>
67
            <class name="ArchiveRootProvider" package="org.openide.filesystems.spi"/>
68
        </change>
52
        <change id="repository.multiuser">
69
        <change id="repository.multiuser">
53
            <api name="filesystems"/>
70
            <api name="filesystems"/>
54
            <summary>Support for multi-user environments</summary>
71
            <summary>Support for multi-user environments</summary>
(-)a/openide.filesystems/nbproject/project.properties (-1 / +1 lines)
Lines 41-47 Link Here
41
# made subject to such option by the copyright holder.
41
# made subject to such option by the copyright holder.
42
42
43
javac.compilerargs=-Xlint -Xlint:-serial
43
javac.compilerargs=-Xlint -Xlint:-serial
44
javac.source=1.7
44
javac.source=1.8
45
module.jar.dir=core
45
module.jar.dir=core
46
javadoc.main.page=org/openide/filesystems/doc-files/api.html
46
javadoc.main.page=org/openide/filesystems/doc-files/api.html
47
javadoc.arch=${basedir}/arch.xml
47
javadoc.arch=${basedir}/arch.xml
(-)a/openide.filesystems/src/META-INF/upgrade/FileUtil.hint (+20 lines)
Lines 10-12 Link Here
10
=>
10
=>
11
$c.setCurrentDirectory($d)
11
$c.setCurrentDirectory($d)
12
;;
12
;;
13
14
//"jar".equals($url.getProtocol()) :: $url instanceof java.net.URL
15
//=>
16
//org.openide.filesystems.FileUtil.isArchiveArtifact($url)
17
//;;
18
19
//$url.getProtocol().equals("jar") :: $url instanceof java.net.URL
20
//=>
21
//org.openide.filesystems.FileUtil.isArchiveArtifact($url)
22
//;;
23
24
//"jar".equals($uri.getScheme()) :: $uri instanceof java.net.URI
25
//=>
26
//org.openide.filesystems.FileUtil.isArchiveArtifact($uri.toURL())
27
//;;
28
29
//$uri.getScheme().equals("jar") :: $uri instanceof java.net.URI
30
//=>
31
//org.openide.filesystems.FileUtil.isArchiveArtifact($uri.toURL())
32
//;;
(-)a/openide.filesystems/src/org/openide/filesystems/FileUtil.java (-145 / +122 lines)
Lines 56-65 Link Here
56
import java.lang.reflect.Method;
56
import java.lang.reflect.Method;
57
import java.net.MalformedURLException;
57
import java.net.MalformedURLException;
58
import java.net.URI;
58
import java.net.URI;
59
import java.net.URISyntaxException;
59
import java.net.URL;
60
import java.net.URL;
60
import java.net.URLStreamHandler;
61
import java.net.URLStreamHandler;
61
import java.util.ArrayList;
62
import java.util.ArrayList;
62
import java.util.Arrays;
63
import java.util.Collection;
63
import java.util.Collection;
64
import java.util.Collections;
64
import java.util.Collections;
65
import java.util.Enumeration;
65
import java.util.Enumeration;
Lines 71-92 Link Here
71
import java.util.Set;
71
import java.util.Set;
72
import java.util.Stack;
72
import java.util.Stack;
73
import java.util.StringTokenizer;
73
import java.util.StringTokenizer;
74
import java.util.WeakHashMap;
75
import java.util.concurrent.Callable;
74
import java.util.concurrent.Callable;
76
import java.util.concurrent.ConcurrentHashMap;
75
import java.util.concurrent.ConcurrentHashMap;
77
import java.util.concurrent.atomic.AtomicBoolean;
76
import java.util.concurrent.atomic.AtomicBoolean;
77
import java.util.concurrent.atomic.AtomicReference;
78
import java.util.jar.JarEntry;
78
import java.util.jar.JarEntry;
79
import java.util.jar.JarInputStream;
79
import java.util.jar.JarInputStream;
80
import java.util.logging.Level;
80
import java.util.logging.Level;
81
import java.util.logging.Logger;
81
import java.util.logging.Logger;
82
import org.netbeans.modules.openide.filesystems.declmime.MIMEResolverImpl;
82
import org.netbeans.modules.openide.filesystems.declmime.MIMEResolverImpl;
83
import org.openide.filesystems.FileSystem.AtomicAction;
83
import org.openide.filesystems.FileSystem.AtomicAction;
84
import org.openide.filesystems.spi.ArchiveRootProvider;
84
import org.openide.util.Exceptions;
85
import org.openide.util.Exceptions;
85
import org.openide.util.NbBundle;
86
import org.openide.util.NbBundle;
86
import org.openide.util.Parameters;
87
import org.openide.util.Parameters;
87
import org.openide.util.RequestProcessor;
88
import org.openide.util.RequestProcessor;
88
import org.openide.util.BaseUtilities;
89
import org.openide.util.BaseUtilities;
90
import org.openide.util.Lookup;
89
import org.openide.util.WeakListeners;
91
import org.openide.util.WeakListeners;
92
import org.openide.util.lookup.Lookups;
93
import org.openide.util.lookup.ProxyLookup;
90
import org.openide.util.lookup.implspi.NamedServicesProvider;
94
import org.openide.util.lookup.implspi.NamedServicesProvider;
91
95
92
/** Common utilities for handling files.
96
/** Common utilities for handling files.
Lines 99-109 Link Here
99
103
100
    private static final Logger LOG = Logger.getLogger(FileUtil.class.getName());
104
    private static final Logger LOG = Logger.getLogger(FileUtil.class.getName());
101
105
102
    /** Normal header for ZIP files. */
103
    private static byte[] ZIP_HEADER_1 = {0x50, 0x4b, 0x03, 0x04};
104
    /** 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 */
105
    private static byte[] ZIP_HEADER_2 = {0x50, 0x4b, 0x05, 0x06};
106
    
107
    /** transient attributes which should not be copied
106
    /** transient attributes which should not be copied
108
    * of type Set<String>
107
    * of type Set<String>
109
    */
108
    */
Lines 125-132 Link Here
125
        transientAttributes.add(MultiFileObject.WEIGHT_ATTRIBUTE); // NOI18N
124
        transientAttributes.add(MultiFileObject.WEIGHT_ATTRIBUTE); // NOI18N
126
    }
125
    }
127
126
128
    /** Cache for {@link #isArchiveFile(FileObject)}. */
129
    private static final Map<FileObject, Boolean> archiveFileCache = new WeakHashMap<FileObject,Boolean>();
130
    private static FileSystem diskFileSystem;
127
    private static FileSystem diskFileSystem;
131
128
132
    static String toDebugString(File file) {
129
    static String toDebugString(File file) {
Lines 1835-1869 Link Here
1835
     * Returns a FileObject representing the root folder of an archive.
1832
     * Returns a FileObject representing the root folder of an archive.
1836
     * Clients may need to first call {@link #isArchiveFile(FileObject)} to determine
1833
     * Clients may need to first call {@link #isArchiveFile(FileObject)} to determine
1837
     * if the file object refers to an archive file.
1834
     * if the file object refers to an archive file.
1838
     * @param fo a ZIP- (or JAR-) format archive file
1835
     * @param fo a java archive file, by default ZIP and JAR are supported
1839
     * @return a virtual archive root folder, or null if the file is not actually an archive
1836
     * @return a virtual archive root folder, or null if the file is not actually an archive
1840
     * @since 4.48
1837
     * @since 4.48
1841
     */
1838
     */
1842
    public static FileObject getArchiveRoot(FileObject fo) {
1839
    public static FileObject getArchiveRoot(FileObject fo) {
1843
        URL archiveURL = URLMapper.findURL(fo, URLMapper.EXTERNAL);
1840
        for (ArchiveRootProvider provider : getArchiveRootProviders()) {
1844
1841
            if (provider.isArchiveFile(fo, false)) {
1845
        if (archiveURL == null) {
1842
                final FileObject root = provider.getArchiveRoot(fo);
1846
            return null;
1843
                if (root != null) {
1844
                    return root;
1845
                }
1846
            }
1847
        }
1847
        }
1848
1848
        return null;
1849
        return URLMapper.findFileObject(getArchiveRoot(archiveURL));
1850
    }
1849
    }
1851
1850
1852
    /**
1851
    /**
1853
     * Returns a URL representing the root of an archive.
1852
     * Returns a URL representing the root of an archive.
1854
     * Clients may need to first call {@link #isArchiveFile(URL)} to determine if the URL
1853
     * Clients may need to first call {@link #isArchiveFile(URL)} to determine if the URL
1855
     * refers to an archive file.
1854
     * refers to an archive file.
1856
     * @param url of a ZIP- (or JAR-) format archive file
1855
     * @param url of a java archive file, by default ZIP and JAR are supported
1857
     * @return the <code>jar</code>-protocol URL of the root of the archive
1856
     * @return the archive (eg. <code>jar</code>) protocol URL of the root of the archive.
1858
     * @since 4.48
1857
     * @since 4.48
1859
     */
1858
     */
1860
    public static URL getArchiveRoot(URL url) {
1859
    public static URL getArchiveRoot(URL url) {
1861
        try {
1860
        for (ArchiveRootProvider provider : getArchiveRootProviders()) {
1862
            // XXX TBD whether the url should ever be escaped...
1861
            if (provider.isArchiveFile(url, false)) {
1863
            return new URL("jar:" + url + "!/"); // NOI18N
1862
                final URL root = provider.getArchiveRoot(url);
1864
        } catch (MalformedURLException e) {
1863
                if (root != null) {
1865
            throw new AssertionError(e);
1864
                    return root;
1865
                }
1866
            }
1866
        }
1867
        }
1868
        //For compatibility reason never return null but return the jar URL.
1869
        return getArchiveRootProviders().iterator().next().getArchiveRoot(url);
1867
    }
1870
    }
1868
1871
1869
    /**
1872
    /**
Lines 1871-2031 Link Here
1871
     * FileObject given by the parameter.
1874
     * FileObject given by the parameter.
1872
     * <strong>Remember</strong> that any path within the archive is discarded
1875
     * <strong>Remember</strong> that any path within the archive is discarded
1873
     * so you may need to check for non-root entries.
1876
     * so you may need to check for non-root entries.
1874
     * @param fo a file in a JAR filesystem
1877
     * @param fo a file in an archive filesystem
1875
     * @return the file corresponding to the archive itself,
1878
     * @return the file corresponding to the archive itself,
1876
     *         or null if <code>fo</code> is not an archive entry
1879
     *         or null if <code>fo</code> is not an archive entry
1877
     * @since 4.48
1880
     * @since 4.48
1878
     */
1881
     */
1879
    public static FileObject getArchiveFile(FileObject fo) {
1882
    public static FileObject getArchiveFile(FileObject fo) {
1880
        Parameters.notNull("fo", fo);   //NOI18N
1883
        Parameters.notNull("fo", fo);   //NOI18N
1881
        try {
1884
        for (ArchiveRootProvider provider : getArchiveRootProviders()) {
1882
            FileSystem fs = fo.getFileSystem();
1885
            if (provider.isArchiveArtifact(fo)) {
1883
1886
                final FileObject file = provider.getArchiveFile(fo);
1884
            if (fs instanceof JarFileSystem) {
1887
                if (file != null) {
1885
                File jarFile = ((JarFileSystem) fs).getJarFile();
1888
                    return file;
1886
1889
                }
1887
                return toFileObject(jarFile);
1888
            }
1890
            }
1889
        } catch (FileStateInvalidException e) {
1890
            Exceptions.printStackTrace(e);
1891
        }
1891
        }
1892
1893
        return null;
1892
        return null;
1894
    }
1893
    }
1895
1894
1896
    /**
1895
    /**
1897
     * Returns the URL of the archive file containing the file
1896
     * Returns the URL of the archive file containing the file
1898
     * referred to by a <code>jar</code>-protocol URL.
1897
     * referred to by an archive (eg. <code>jar</code>) protocol URL.
1899
     * <strong>Remember</strong> that any path within the archive is discarded
1898
     * <strong>Remember</strong> that any path within the archive is discarded
1900
     * so you may need to check for non-root entries.
1899
     * so you may need to check for non-root entries.
1901
     * @param url a URL
1900
     * @param url a URL
1902
     * @return the embedded archive URL, or null if the URL is not a
1901
     * @return the embedded archive URL, or null if the URL is not an
1903
     *         <code>jar</code>-protocol URL containing <code>!/</code>
1902
     *         archive protocol URL containing <code>!/</code>
1904
     * @since 4.48
1903
     * @since 4.48
1905
     */
1904
     */
1906
    public static URL getArchiveFile(URL url) {
1905
    public static URL getArchiveFile(URL url) {
1907
        String protocol = url.getProtocol();
1906
        for (ArchiveRootProvider provider : getArchiveRootProviders()) {
1908
1907
            if (provider.isArchiveArtifact(url)) {
1909
        if ("jar".equals(protocol)) { //NOI18N
1908
                final URL file = provider.getArchiveFile(url);
1910
1909
                if (file != null) {
1911
            String path = url.getPath();
1910
                    return file;
1912
            int index = path.indexOf("!/"); //NOI18N
1913
1914
            if (index >= 0) {
1915
                String jarPath = null;
1916
                try {
1917
                    jarPath = path.substring(0, index);
1918
                    if (jarPath.indexOf("file://") > -1 && jarPath.indexOf("file:////") == -1) {  //NOI18N
1919
                        /* Replace because JDK application classloader wrongly recognizes UNC paths. */
1920
                        jarPath = jarPath.replaceFirst("file://", "file:////");  //NOI18N
1921
                    }
1922
                    return new URL(jarPath);
1923
1924
                } catch (MalformedURLException mue) {                    
1925
                    LOG.log(
1926
                        Level.WARNING,
1927
                        "Invalid URL ({0}): {1}, jarPath: {2}", //NOI18N
1928
                        new Object[] {
1929
                            mue.getMessage(),
1930
                            url.toExternalForm(),
1931
                            jarPath
1932
                        });
1933
                }
1911
                }
1934
            }
1912
            }
1935
        }
1913
        }
1936
1937
        return null;
1914
        return null;
1938
    }
1915
    }
1939
1916
1940
    /**
1917
    /**
1941
     * Tests if a file represents a JAR or ZIP archive.
1918
     * Tests if a file represents a java archive.
1919
     * By default the JAR or ZIP archives are supported.
1942
     * @param fo the file to be tested
1920
     * @param fo the file to be tested
1943
     * @return true if the file looks like a ZIP-format archive
1921
     * @return true if the file looks like a java archive
1944
     * @since 4.48
1922
     * @since 4.48
1945
     */
1923
     */
1946
    public static boolean isArchiveFile(FileObject fo) {
1924
    public static boolean isArchiveFile(FileObject fo) {
1947
        Parameters.notNull("fileObject", fo);  //NOI18N
1925
        Parameters.notNull("fileObject", fo);  //NOI18N
1948
1926
        for (ArchiveRootProvider provider : getArchiveRootProviders()) {
1949
        if (!fo.isValid()) {
1927
            if (provider.isArchiveFile(fo, true)) {
1950
            return isArchiveFile(fo.getPath());
1928
                return true;
1929
            }
1951
        }
1930
        }
1952
        // XXX Special handling of virtual file objects: try to determine it using its name, but don't cache the
1931
        return false;
1953
        // result; when the file is checked out the more correct method can be used
1954
        if (fo.isVirtual()) {
1955
            return isArchiveFile(fo.getPath());
1956
        }
1957
1958
        if (fo.isFolder()) {
1959
            return false;
1960
        }
1961
1962
        // First check the cache.
1963
        Boolean b = archiveFileCache.get(fo);
1964
1965
        if (b == null) {
1966
            // Need to check it.
1967
            try {
1968
                InputStream in = fo.getInputStream();
1969
1970
                try {
1971
                    byte[] buffer = new byte[4];
1972
                    int len = in.read(buffer, 0, 4);
1973
1974
                    if (len == 4) {
1975
                        // Got a header, see if it is a ZIP file.
1976
                        b = Boolean.valueOf(Arrays.equals(ZIP_HEADER_1, buffer) || Arrays.equals(ZIP_HEADER_2, buffer));
1977
                    } else {
1978
                        //If the length is less than 4, it can be either
1979
                        //broken (empty) archive file or other empty file.
1980
                        //Return false and don't cache it, when the archive
1981
                        //file will be written and closed its length will change
1982
                        return false;
1983
                    }
1984
                } finally {
1985
                    in.close();
1986
                }
1987
            } catch (IOException ioe) {
1988
                // #160507 - ignore exception (e.g. permission denied)
1989
                LOG.log(Level.FINE, null, ioe);
1990
            }
1991
1992
            if (b == null) {
1993
                b = isArchiveFile(fo.getPath());
1994
            }
1995
1996
            archiveFileCache.put(fo, b);
1997
        }
1998
1999
        return b.booleanValue();
2000
    }
1932
    }
2001
1933
2002
    /**
1934
    /**
2003
     * Tests if a URL represents a JAR or ZIP archive.
1935
     * Tests if a URL represents a java archive.
1936
     * By default the JAR or ZIP archives are supported.
2004
     * If there is no such file object, the test is done by heuristic: any URL with an extension is
1937
     * If there is no such file object, the test is done by heuristic: any URL with an extension is
2005
     * treated as an archive.
1938
     * treated as an archive.
2006
     * @param url a URL to a file
1939
     * @param url a URL to a file
2007
     * @return true if the URL seems to represent a ZIP-format archive
1940
     * @return true if the URL seems to represent a java archive
2008
     * @since 4.48
1941
     * @since 4.48
2009
     */
1942
     */
2010
    public static boolean isArchiveFile(URL url) {
1943
    public static boolean isArchiveFile(URL url) {
2011
        Parameters.notNull("url", url);  //NOI18N
1944
        Parameters.notNull("url", url);  //NOI18N
1945
        return isArchiveFileImpl(url, true);
1946
    }
2012
1947
2013
        if ("jar".equals(url.getProtocol())) { //NOI18N
1948
    /**
1949
     * Tests if an file is inside an archive.
1950
     * @param fo the file to be tested
1951
     * @return true if the file is inside an archive
1952
     * @since 9.6
1953
     */
1954
    public static boolean isArchiveArtifact(FileObject fo) {
1955
        Parameters.notNull("fileObject", fo);  //NOI18N
1956
        for (ArchiveRootProvider provider : getArchiveRootProviders()) {
1957
            if (provider.isArchiveArtifact(fo)) {
1958
                return true;
1959
            }
1960
        }
1961
        return false;
1962
    }
2014
1963
2015
            //Already inside archive, return false
1964
    /**
2016
            return false;
1965
     * Tests if an {@link URL} denotes a file inside an archive.
1966
     * @param url the url to be tested
1967
     * @return true if the url points inside an archive
1968
     * @since 9.6
1969
     */
1970
    public static boolean isArchiveArtifact(URL url) {
1971
        Parameters.notNull("url", url);  //NOI18N
1972
        for (ArchiveRootProvider provider : getArchiveRootProviders()) {
1973
            if (provider.isArchiveArtifact(url)) {
1974
                return true;
1975
            }
2017
        }
1976
        }
2018
1977
        return false;
2019
        FileObject fo = URLMapper.findFileObject(url);
2020
2021
        if ((fo != null) && !fo.isVirtual()) {
2022
            if (LOG.isLoggable(Level.FINEST)) {
2023
                LOG.log(Level.FINEST, "isArchiveFile_FILE_RESOLVED", fo); //NOI18N, used by FileUtilTest.testIsArchiveFileRace
2024
            }
2025
            return isArchiveFile(fo);
2026
        } else {
2027
            return isArchiveFile(url.getPath());
2028
        }
2029
    }
1978
    }
2030
1979
2031
    /**
1980
    /**
Lines 2048-2054 Link Here
2048
                u = BaseUtilities.toURI(entry).toURL();
1997
                u = BaseUtilities.toURI(entry).toURL();
2049
                isDir = entry.isDirectory();
1998
                isDir = entry.isDirectory();
2050
            } while (wasDir ^ isDir);
1999
            } while (wasDir ^ isDir);
2051
            if (isArchiveFile(u) || entry.isFile() && entry.length() < 4) {
2000
            if (isArchiveFileImpl(u, false)) {
2052
                return getArchiveRoot(u);
2001
                return getArchiveRoot(u);
2053
            } else if (isDir) {
2002
            } else if (isDir) {
2054
                assert u.toExternalForm().endsWith("/");    //NOI18N
2003
                assert u.toExternalForm().endsWith("/");    //NOI18N
Lines 2078-2086 Link Here
2078
     * @since org.openide.filesystems 7.8
2027
     * @since org.openide.filesystems 7.8
2079
     */
2028
     */
2080
    public static File archiveOrDirForURL(URL entry) {
2029
    public static File archiveOrDirForURL(URL entry) {
2081
        String u = entry.toString();
2030
        final String u = entry.toString();
2082
        if (u.startsWith("jar:file:") && u.endsWith("!/")) { // NOI18N
2031
        if (isArchiveArtifact(entry)) {
2083
            return BaseUtilities.toFile(URI.create(u.substring(4, u.length() - 2)));
2032
            entry = getArchiveFile(entry);
2033
            try {
2034
                return u.endsWith("!/") && entry != null && "file".equals(entry.getProtocol()) ?  //NOI18N
2035
                    BaseUtilities.toFile(entry.toURI()):
2036
                    null;
2037
            } catch (URISyntaxException e) {
2038
                LOG.log(
2039
                        Level.WARNING,
2040
                        "Invalid URI: {0} ({1})",   //NOI18N
2041
                        new Object[]{
2042
                            entry,
2043
                            e.getMessage()
2044
                        });
2045
                return null;
2046
            }
2084
        } else if (u.startsWith("file:")) { // NOI18N
2047
        } else if (u.startsWith("file:")) { // NOI18N
2085
            return BaseUtilities.toFile(URI.create(u));
2048
            return BaseUtilities.toFile(URI.create(u));
2086
        } else {
2049
        } else {
Lines 2361-2373 Link Here
2361
        }
2324
        }
2362
    }
2325
    }
2363
2326
2364
    /**
2327
    private static boolean isArchiveFileImpl(final URL url, final boolean strict) {
2365
     * Tests if a non existent path represents a file.
2328
        for (ArchiveRootProvider provider : getArchiveRootProviders()) {
2366
     * @param path to be tested, separated by '/'.
2329
            if (provider.isArchiveFile(url, strict)) {
2367
     * @return true if the file has '.' after last '/'.
2330
                return true;
2368
     */
2331
            }
2369
    private static boolean isArchiveFile (final String path) {
2332
        }
2370
        int index = path.lastIndexOf('.');  //NOI18N
2333
        return false;
2371
        return (index != -1) && (index > path.lastIndexOf('/') + 1);    //NOI18N
2372
    }
2334
    }
2335
2336
    private static Iterable<? extends ArchiveRootProvider> getArchiveRootProviders() {
2337
        Lookup.Result<ArchiveRootProvider> res = archiveRootProviders.get();
2338
        if (res == null) {
2339
            res = new ProxyLookup(
2340
                Lookups.singleton(new JarArchiveRootProvider()),
2341
                Lookup.getDefault()).lookupResult(ArchiveRootProvider.class);
2342
            if (!archiveRootProviders.compareAndSet(null, res)) {
2343
                res = archiveRootProviders.get();
2344
            }
2345
        }
2346
        return res.allInstances();
2347
    }
2348
2349
    private static final AtomicReference<Lookup.Result<ArchiveRootProvider>> archiveRootProviders = new AtomicReference<>();
2373
}
2350
}
(-)a/openide.filesystems/src/org/openide/filesystems/JarArchiveRootProvider.java (+214 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2015 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2015 Sun Microsystems, Inc.
41
 */
42
package org.openide.filesystems;
43
44
import java.io.File;
45
import java.io.IOException;
46
import java.io.InputStream;
47
import java.net.MalformedURLException;
48
import java.net.URL;
49
import java.util.Arrays;
50
import java.util.Collections;
51
import java.util.Map;
52
import java.util.WeakHashMap;
53
import java.util.logging.Level;
54
import java.util.logging.Logger;
55
import org.openide.filesystems.spi.ArchiveRootProvider;
56
import org.openide.util.Exceptions;
57
58
/**
59
 *
60
 * @author Tomas Zezula
61
 */
62
final class JarArchiveRootProvider implements ArchiveRootProvider {
63
    private static final String PROTOCOL = "jar";   //NOI18N
64
    /** Normal header for ZIP files. */
65
    private static byte[] ZIP_HEADER_1 = {0x50, 0x4b, 0x03, 0x04};
66
    /** 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 */
67
    private static byte[] ZIP_HEADER_2 = {0x50, 0x4b, 0x05, 0x06};
68
    private static final Logger LOG = Logger.getLogger(JarArchiveRootProvider.class.getName());
69
    /** Cache for {@link #isArchiveFile(FileObject)}. */
70
    private static final Map</*@GuardedBy("archiveFileCache")*/FileObject, Boolean> archiveFileCache = Collections.synchronizedMap(new WeakHashMap<FileObject,Boolean>());
71
72
    @Override
73
    public boolean isArchiveFile(URL url, boolean strict) {
74
        if (PROTOCOL.equals(url.getProtocol())) { //NOI18N
75
            //Already inside archive, return false
76
            return false;
77
        }
78
        FileObject fo = URLMapper.findFileObject(url);
79
        if ((fo != null) && !fo.isVirtual()) {
80
            if (LOG.isLoggable(Level.FINEST)) {
81
                LOG.log(Level.FINEST, "isArchiveFile_FILE_RESOLVED", fo); //NOI18N, used by FileUtilTest.testIsArchiveFileRace
82
            }
83
            return isArchiveFile(fo, strict);
84
        } else {
85
            return isArchiveFile(url.getPath());
86
        }
87
    }
88
89
    @Override
90
    public boolean isArchiveFile(FileObject fo, boolean strict) {
91
        if (!fo.isValid()) {
92
            return isArchiveFile(fo.getPath());
93
        }
94
        // XXX Special handling of virtual file objects: try to determine it using its name, but don't cache the
95
        // result; when the file is checked out the more correct method can be used
96
        if (fo.isVirtual()) {
97
            return isArchiveFile(fo.getPath());
98
        }
99
100
        if (fo.isFolder()) {
101
            return false;
102
        }
103
104
        // First check the cache.
105
        Boolean b = archiveFileCache.get(fo);
106
        if (b == null) {
107
            // Need to check it.
108
            try {
109
                InputStream in = fo.getInputStream();
110
111
                try {
112
                    byte[] buffer = new byte[4];
113
                    int len = in.read(buffer, 0, 4);
114
115
                    if (len == 4) {
116
                        // Got a header, see if it is a ZIP file.
117
                        b = Boolean.valueOf(Arrays.equals(ZIP_HEADER_1, buffer) || Arrays.equals(ZIP_HEADER_2, buffer));
118
                    } else {
119
                        //If the length is less than 4, it can be either
120
                        //broken (empty) archive file or other empty file.
121
                        //Return false and don't cache it, when the archive
122
                        //file will be written and closed its length will change
123
                        return !strict;
124
                    }
125
                } finally {
126
                    in.close();
127
                }
128
            } catch (IOException ioe) {
129
                // #160507 - ignore exception (e.g. permission denied)
130
                LOG.log(Level.FINE, null, ioe);
131
            }
132
            if (b == null) {
133
                b = isArchiveFile(fo.getPath());
134
            }
135
            archiveFileCache.put(fo, b);
136
        }
137
        return b.booleanValue();
138
    }
139
140
141
    @Override
142
    public boolean isArchiveArtifact(URL url) {
143
        return PROTOCOL.equals(url.getProtocol());
144
    }
145
146
    @Override
147
    public URL getArchiveFile(URL url) {
148
        String protocol = url.getProtocol();
149
150
        if (PROTOCOL.equals(protocol)) { //NOI18N
151
152
            String path = url.getPath();
153
            int index = path.indexOf("!/"); //NOI18N
154
155
            if (index >= 0) {
156
                String jarPath = null;
157
                try {
158
                    jarPath = path.substring(0, index);
159
                    if (jarPath.indexOf("file://") > -1 && jarPath.indexOf("file:////") == -1) {  //NOI18N
160
                        /* Replace because JDK application classloader wrongly recognizes UNC paths. */
161
                        jarPath = jarPath.replaceFirst("file://", "file:////");  //NOI18N
162
                    }
163
                    return new URL(jarPath);
164
165
                } catch (MalformedURLException mue) {
166
                    LOG.log(
167
                        Level.WARNING,
168
                        "Invalid URL ({0}): {1}, jarPath: {2}", //NOI18N
169
                        new Object[] {
170
                            mue.getMessage(),
171
                            url.toExternalForm(),
172
                            jarPath
173
                        });
174
                }
175
            }
176
        }
177
        return null;
178
    }
179
180
    @Override
181
    public FileObject getArchiveFile(FileObject fo) {
182
        try {
183
            final FileSystem fs = fo.getFileSystem();
184
            if (fs instanceof JarFileSystem) {
185
                final File jarFile = ((JarFileSystem) fs).getJarFile();
186
                return FileUtil.toFileObject(jarFile);
187
            }
188
        } catch (FileStateInvalidException e) {
189
            Exceptions.printStackTrace(e);
190
        }
191
        return null;
192
    }
193
194
    @Override
195
    public URL getArchiveRoot(final URL url) {
196
        try {
197
            // XXX TBD whether the url should ever be escaped...
198
            return new URL("jar:" + url + "!/"); // NOI18N
199
        } catch (MalformedURLException e) {
200
            throw new AssertionError(e);
201
        }
202
    }
203
204
    /**
205
     * Tests if a non existent path represents a file.
206
     * @param path to be tested, separated by '/'.
207
     * @return true if the file has '.' after last '/'.
208
     */
209
    private static boolean isArchiveFile (final String path) {
210
        int index = path.lastIndexOf('.');  //NOI18N
211
        return (index != -1) && (index > path.lastIndexOf('/') + 1);    //NOI18N
212
    }
213
214
}
(-)a/openide.filesystems/src/org/openide/filesystems/spi/ArchiveRootProvider.java (+156 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2015 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2015 Sun Microsystems, Inc.
41
 */
42
package org.openide.filesystems.spi;
43
44
import java.net.URL;
45
import org.openide.filesystems.FileObject;
46
import org.openide.filesystems.FileUtil;
47
import org.openide.filesystems.URLMapper;
48
49
/**
50
 * A possibility to plug a support for java archives into FileUtil.
51
 * The interface is used by {@link FileUtil.isArchiveArtifact}, {@link FileUtil.isArchiveFile},
52
 * {@link FileUtil.getArchiveRoot}, {@link FileUtil.getArchiveFile}.
53
 * The implementations are registered in global lookup.
54
 * @author Tomas Zezula
55
 * @since 9.6
56
 */
57
public interface ArchiveRootProvider {
58
59
    /**
60
     * Tests if a file represents an java archive.
61
     * @param url the file to be tested
62
     * @param strict when false the detection may not be precise, for example
63
     * an empty archive missing the archive header is treated as an archive
64
     * @return true if the file looks like an archive
65
     */
66
    boolean isArchiveFile(URL url, boolean strict);
67
68
    /**
69
     * Tests if a file represents an java archive.
70
     * The default implementation delegates to {@link ArchiveRootProvider#isArchiveFile(URL, boolean)},
71
     * it can be overridden by an implementation in more efficient way.
72
     * @param fo the file to be tested
73
     * @param strict when false the detection may not be precise, for example
74
     * an empty archive missing the archive header is treated as an archive
75
     * @return true if the file looks like an archive
76
     */
77
    default boolean isArchiveFile(FileObject fo, boolean strict) {
78
        final URL url = URLMapper.findURL(fo, URLMapper.EXTERNAL);
79
        return url == null ? false : isArchiveFile(url, strict);
80
    }
81
82
    /**
83
     * Tests if an {@link URL} denotes a file inside an archive.
84
     * @param url the url to be tested
85
     * @return true if the url points inside an archive
86
     */
87
    boolean isArchiveArtifact(URL url);
88
89
    /**
90
     * Tests if an file is inside an archive.
91
     * The default implementation delegates to {@link ArchiveRootProvider#isArchiveArtifact(URL)},
92
     * it can be overridden by an implementation in more efficient way.
93
     * @param fo the file to be tested
94
     * @return true if the file is inside an archive
95
     */
96
    default boolean isArchiveArtifact(FileObject fo) {
97
        final URL url = URLMapper.findURL(fo, URLMapper.EXTERNAL);
98
        return url == null ? false : isArchiveArtifact(url);
99
    }
100
101
    /**
102
     * Returns the URL of the archive file containing the file
103
     * referred to by a archive-protocol URL.
104
     * <strong>Remember</strong> that any path within the archive is discarded
105
     * so you may need to check for non-root entries.
106
     * @param url a URL
107
     * @return the embedded archive URL, or null if the URL is not an
108
     *         archive-protocol URL containing <code>!/</code>
109
     */
110
    URL getArchiveFile(URL url);
111
112
    /**
113
     * Returns a FileObject representing an archive file containing the
114
     * FileObject given by the parameter.
115
     * <strong>Remember</strong> that any path within the archive is discarded
116
     * so you may need to check for non-root entries.
117
     * The default implementation delegates to {@link ArchiveRootProvider#getArchiveFile(URL)},
118
     * it can be overridden by an implementation in more efficient way.
119
     * @param fo a file in a archive filesystem
120
     * @return the file corresponding to the archive itself,
121
     *         or null if <code>fo</code> is not an archive entry
122
     */
123
    default FileObject getArchiveFile(FileObject fo) {
124
        final URL rootURL = URLMapper.findURL(fo, URLMapper.EXTERNAL);
125
        if (rootURL == null) {
126
            return null;
127
        }
128
        return URLMapper.findFileObject(FileUtil.getArchiveFile(rootURL));
129
    }
130
131
    /**
132
     * Returns an URL representing the root of an archive.
133
     * Clients may need to first call {@link #isArchiveFile(URL)} to determine if the URL
134
     * refers to an archive file.
135
     * @param url of an java archive file
136
     * @return the archive-protocol URL of the root of the archive
137
     */
138
    URL getArchiveRoot(URL url);
139
140
    /**
141
     * Returns a FileObject representing the root folder of an archive.
142
     * Clients may need to first call {@link #isArchiveFile(FileObject)} to determine
143
     * if the file object refers to an archive file.
144
     * The default implementation delegates to {@link ArchiveRootProvider#getArchiveRoot(URL)},
145
     * it can be overridden by an implementation in more efficient way.
146
     * @param fo an java archive file
147
     * @return a virtual archive root folder, or null if the file is not actually an archive
148
     */
149
    default FileObject getArchiveRoot(FileObject fo) {
150
        final URL archiveURL = URLMapper.findURL(fo, URLMapper.EXTERNAL);
151
        if (archiveURL == null) {
152
            return null;
153
        }
154
        return URLMapper.findFileObject(FileUtil.getArchiveRoot(archiveURL));
155
    }
156
}
(-)a/openide.filesystems/test/unit/src/org/openide/filesystems/FileUtilTest.java (-1 / +1 lines)
Lines 276-282 Link Here
276
        final File testFile = new File (wd,"test.jar"); //NOI18N
276
        final File testFile = new File (wd,"test.jar"); //NOI18N
277
        FileUtil.createData(testFile);
277
        FileUtil.createData(testFile);
278
278
279
        final Logger log = Logger.getLogger(FileUtil.class.getName());
279
        final Logger log = Logger.getLogger(JarArchiveRootProvider.class.getName());
280
        log.setLevel(Level.FINEST);
280
        log.setLevel(Level.FINEST);
281
        final Handler handler = new Handler() {
281
        final Handler handler = new Handler() {
282
            @Override
282
            @Override

Return to bug 252644