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 |
} |