package jef.tools;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import jef.common.log.LogUtil;
import jef.tools.reflect.ClassUtils;
import jef.tools.resource.ClassRelativeLoader;
import jef.tools.resource.ClasspathLoader;
import jef.tools.resource.CompsiteLoader;
import jef.tools.resource.FileResource;
import jef.tools.resource.FileSearchLoader;
import jef.tools.resource.IResource;
import jef.tools.resource.PathMatchingResourcePatternResolver;
import jef.tools.resource.Resource;
import jef.tools.resource.ResourceLoader;
import jef.tools.resource.ResourcePatternResolver;
import jef.tools.resource.UrlResourceLoader;
import jef.tools.resource.UrlResourceLoader.EnvURL;
/**
* 资源定位和加载工具,原先在IOUtils当中,后来重新对资源定位模型的功能需求和API进行了设计。从IOUtils中独立出来。
* 新的统一资源定位将重新分析资源定位的需求。然后重新抽象出资源加载策略来实现。满足 任意协议/任意资源/策略自由组合/任意环境等复杂条件,
* 成为放置四海皆便利的最佳实践。
*
* 总结:资源定位加载无非以下几种场景
*
* 一、classpath类: 1.1: 全classpath搜索,搜到哪个在前面就是哪个 1.2 全classpath搜索,搜到匹配的资源全部返回 1.3
* 某个指定类的classpath,仅搜索该类文件所在的classpath。(一般为该类文件的相同目录下,但应该可以搜索到该类所在cp的根路径下)
* 二、web类 即web工程的主目录,或者WEB-INF目录。注意虽然大部分web容器都将classes,lib定位于WEB-INF目录下,
* 但实际上Java规范无此约定。 因此不能假定classpath上溯可以得到WEB-INF目录,也不能假定WEB-INF下面还有classes和lib目录。
*
* 三、运行目录类
*
*
* 四、虚拟机参数、环境变量类
*
*
* 五、osgi bundle类 之前在开发Eclipse插件时做过特殊处理支持,目前看来还是不合适。此版本开始暂不支持,今后再说
*
* 六、上述一种或多种的混合。
*
*
*
*
* -------------------------- 考虑需要满足的复杂资源定位场景 1、在Eclipse中作为常规工程
* 2、发布为一个可执行的jar(fatjar) 3. 多个jar在一个目录中的可执行程序 4.
* 在eclipse中被依赖的工程和当前工程下都有的资源。能根据需要定位 5. 在Tomcat中作为一个war 6.
* 在WL中作为一个war(war包直接发布) 7. 在WL中作为一个发布目录(war包目录发布) 8. 在EJB中作为一个公共库 9
* 在WL中为一个EJB模块 --------------------------
* JEF早期版本在设计时,考虑到classpath的特殊性(CP有目录和压缩文件两种存储方式,而后者很难支持资源文件修改),
* 因此设计了向上级路径查找的规则,虽然在一定范围内取得了实用效果,但终究是非主流用法。
* 从这个版本开始不再兼容。如果用户要确保资源定位必须可写,那么采用后几种方式来设计资源位置比较好。
*
*
* 我们在资源定位设计中常碰到的问题 1/
* 资源冲突。尤其是使用全局classpath定位的时候,这让Asiainfo的开发模式下EasyFrame的故障分析成为一个噩梦。 2/ 资源路径计算错误。
*
* -----
*
* 设计: 新的资源加载/存储API将结合设计模式实现更灵活的加载策略。
*
* @author jiyi
*
*/
public class ResourceUtils {
/** Pseudo URL prefix for loading from the class path: "classpath:" */
public static final String CLASSPATH_URL_PREFIX = "classpath:";
/** URL prefix for loading from the file system: "file:" */
public static final String FILE_URL_PREFIX = "file:";
/** URL protocol for a file in the file system: "file" */
public static final String URL_PROTOCOL_FILE = "file";
/** URL protocol for an entry from a jar file: "jar" */
public static final String URL_PROTOCOL_JAR = "jar";
/** URL protocol for an entry from a zip file: "zip" */
public static final String URL_PROTOCOL_ZIP = "zip";
/** URL protocol for an entry from a JBoss jar file: "vfszip" */
public static final String URL_PROTOCOL_VFSZIP = "vfszip";
/** URL protocol for a JBoss VFS resource: "vfs" */
public static final String URL_PROTOCOL_VFS = "vfs";
/** URL protocol for an entry from a WebSphere jar file: "wsjar" */
public static final String URL_PROTOCOL_WSJAR = "wsjar";
/** URL protocol for an entry from an OC4J jar file: "code-source" */
public static final String URL_PROTOCOL_CODE_SOURCE = "code-source";
/** Separator between JAR URL and file path within the JAR */
public static final String JAR_URL_SEPARATOR = "!/";
private static final ResourceLoader LOADER_DefaultCp = new ClasspathLoader();
private static final ResourceLoader LOADER_DirOnlyCp = new ClasspathLoader(true);
private static final CompsiteLoader LOADER_DIR_FIRST = new CompsiteLoader(LOADER_DirOnlyCp, LOADER_DefaultCp).setFirstCollection(false);
private static final ResourceLoader LOADER_USERDIR = new UrlResourceLoader(EnvURL.USER_DIR);
private static final ResourceLoader LOADER_USERHOME = new UrlResourceLoader(EnvURL.USER_HOME);
private static final ResourceLoader LOADER_SYS = new UrlResourceLoader(EnvURL.PATH, EnvURL.JAVA_LIBRARY_PATH);
private static final ResourceLoader Loader_searchUserDir = new FileSearchLoader(new File(System.getProperty("user.dir")));
/**
* 在指定的class的ClassLoader的载入路径上提取资源
*
* <pre>
* 解释:这个操作只会在指定的class所属的classloader拥有的路径上查找资源。
* 在一个应用环境中,可能会有多级的classloader,使用这个操作相当于指定了要使用的classloader。
* 除此之外,这个操作还有一个特点,也就是说总是会优先查找指定class所在的目录或jar包。这一点在多个路径上都有同名资源时尤其有用。
* </pre>
*
* @param name
* @return
*/
public static URL getResource(Class<?> c, String name) {
return getResource(c, name, false);
}
/**
* 在指定的class的ClassLoader的载入路径上提取资源,返回Resource对象<br>
* 参考{@link #getResource}
*
* @param c
* @param name
* @return
*/
public static Resource getResourceEx(Class<?> c, String name) {
return Resource.getResource(getResource(c, name));
}
/**
* 获得相对于某个class同路径下的资源,可使用相对路径
*
* @param c
* @param name
* @return
*/
public static Resource getClassResource(Class<?> c, String name) {
ClassRelativeLoader loader = new ClassRelativeLoader(c);
return loader.getResourceEx(name);
}
/**
* 得到程序运行目录下的资源 由于这种场合都用于获取文件资源,所以直接返回file对象。
*
* @param name
* @return
*/
public static File getDirResource(String name) {
FileResource file = Resource.getFileResource(LOADER_USERDIR.getResource(name));
return file == null ? null : file.getFile();
}
/**
* 得到用户目录下(不是程序运行目录)下的资源 由于这种场合都用于获取文件资源,所以直接返回file对象。
*
* @param name
* @return
*/
public static File getUserHomeResource(String name) {
FileResource file = Resource.getFileResource(LOADER_USERHOME.getResource(name));
return file == null ? null : file.getFile();
}
/**
* 在系统(操作系统)的path和程序的classpath所有目录下获取资源
*
* @param name
* @return
*/
public static Resource getSystemResource(String name) {
return LOADER_SYS.getResourceEx(name);
}
/**
* 得到程序运行目录下的资源 搜索获取
*
* @param name
* @return
*/
public static URL getDirResourceHierarchy(String name) {
return Loader_searchUserDir.getResource(name);
}
/**
* 在指定的class的loader的相对路径查找资源。 资源不要带"/"前缀,默认都是从根路径查找
*
* @param c
* 类文件
* @param name
* 路径
* @param dirOnly
* 如果此选项设置为true,则只从目录中寻找文件
* @return 资源文件
*/
public static URL getResource(Class<?> c, String name, boolean dirOnly) {
if (c == null)
return getResource(name, dirOnly);
name = name.replace('\\', '/');
CompsiteLoader loader = new CompsiteLoader();
if (dirOnly) {
loader.addResourceLoader(new UrlResourceLoader(c, true));
loader.addResourceLoader(new ClasspathLoader(true, c.getClassLoader()));
} else {
loader.addResourceLoader(new UrlResourceLoader(c)); // 优先指定路径
loader.addResourceLoader(new ClasspathLoader(true, c.getClassLoader()));// 其次目录
loader.addResourceLoader(new ClasspathLoader(false, c.getClassLoader()));// 再次全部
}
return loader.getResource(name);
}
/**
* 无论资源以什么形态存在,总是以文件形式返回
*
* @param name
* @return
*/
public static File getResourceFile(String name) {
FileResource fs = Resource.getFileResource(getResource(name));
return fs == null ? null : fs.getFile();
}
/**
* 获得资源<br>
* 相当于调用 getResource(name,false, new ClassLoader[0]);<br>
* 参考{@link #getResource(String, boolean, ClassLoader...)}
*
* @param name
* @return
*/
public static URL getResource(String name) {
return getResource(name, false);
}
/**
* 获得全部资源 相当于调用 getResources(name,false, new ClassLoader[0]);<br>
* 参考{@link #getResources(String, boolean, ClassLoader...)}
*
* @param name
* @return
*/
public static List<URL> getResources(String name) {
return getResources(name, false);
}
/**
* 将找到的资源封装为resource对象 参考{@link #getResource(String)}
*
* @param name
* @return
*/
public static Resource getResourceEx(String name) {
return Resource.getResource(getResource(name, false));
}
/**
* 在指定的路径上查找资源文件 从生产实践讲,放在目录下的文件由于便于维护,比放在JAR包中的文件享有绝对优先权。
*
* @param name
* 资源名称,资源名称可以采用下列格式之一
* <table width=90% border=1>
* <tr>
* <td>resource-path/name</td>
* <td width="80%">即classpath下的resource-path/name资源</td>
* </tr>
* <tr>
* <td>/resource-path/name</td>
* <td>第一个斜杠会自动被忽略,结果同上(请避免这样用)</td>
* </tr>
* <tr>
* <td>classpath*:resource-path/name</td>
* <td>“classpath*:”会被忽略,结果同上</td>
* </tr>
* <tr>
* <td>classpath:resource-path/name</td>
* <td>“classpath:” 会被忽略,结果同上</td>
* </tr>
* <tr>
* <td>file:resource-path/name</td>
* <td>会在当前程序运行目录下寻找资源</td>
* </tr>
*
* <tr>
* <td>file:/resource-path/name</td>
* <td>第一个斜杠会自动被忽略,结果同上</td>
* </tr>
* <tr>
* <td>file://c:/resource-path/name</td>
* <td>第一个斜杠被忽略,第二个斜杠表明这是绝对路径,按绝对路径查找文件</td>
* </tr>
* <tr>
* <td>http://localhost:80/context/name</td>
* <td>直接以这个URL作为资源</td>
* </tr>
* <tr>
* <td>ftp://user:password@host:21/name</td>
* <td>直接以这个URL作为资源</td>
* </tr>
* </table>
* @param dironly
* 为true只查找目录中的资源,放弃jar包或zip包的资源,以及http等网络资源(仅当查找classpath类资源有效)
* @param loaders
* 指定类加载器,将使用这些类加载器来查找资源 (仅当查找classpath类资源有效)
* @return
*/
public final static URL getResource(String name, boolean dironly, ClassLoader... loaders) {
if (name.startsWith("classpath*:")) {
name = name.substring(11);
} else if (name.startsWith("classpath:")) {
name = name.substring(10);
} else if (name.startsWith("file:")) {
name = name.substring(5);
URL u = LOADER_USERDIR.getResource(name);
if (u != null) {
return u;
} else {
return null;
}
} else if (name.indexOf("://") > -1) {
try {
return new URL(name);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(name + " is invalid! " + e.getMessage());
}
}
if (loaders.length == 0) {
return dironly ? LOADER_DirOnlyCp.getResource(name) : LOADER_DIR_FIRST.getResource(name);
}
ResourceLoader loader = new ClasspathLoader(true, loaders);
if (dironly)
return loader.getResource(name);
loader = new CompsiteLoader(loader, new ClasspathLoader(false, loaders));
return loader.getResource(name);
}
/**
* 在指定的路径上查找所有入选资源文件
*
* @param name
* <table width=90% border=1>
* <tr>
* <td>resource-path/name</td>
* <td width="80%">即classpath下的resource-path/name资源</td>
* </tr>
* <tr>
* <td>/resource-path/name</td>
* <td>第一个斜杠会自动被忽略,结果同上(请避免这样用)</td>
* </tr>
* <tr>
* <td>classpath*:resource-path/name</td>
* <td>“classpath*:”会被忽略,结果同上</td>
* </tr>
* <tr>
* <td>classpath:resource-path/name</td>
* <td>“classpath:” 会被忽略,结果同上</td>
* </tr>
* <tr>
* <td>file:resource-path/name</td>
* <td>会在当前程序运行目录下寻找资源</td>
* </tr>
*
* <tr>
* <td>file:/resource-path/name</td>
* <td>第一个斜杠会自动被忽略,结果同上</td>
* </tr>
* <tr>
* <td>file://c:/resource-path/name</td>
* <td>第一个斜杠被忽略,第二个斜杠表明这是绝对路径,按绝对路径查找文件</td>
* </tr>
* <tr>
* <td>http://localhost:80/context/name</td>
* <td>直接以这个URL作为资源</td>
* </tr>
* <tr>
* <td>ftp://user:password@host:21/name</td>
* <td>直接以这个URL作为资源</td>
* </tr>
* </table>
* @param dironly
* 为true只查找目录中的资源,放弃jar包或zip包的资源,以及http等网络资源(仅当查找classpath类资源有效)
* @param loaders
* 指定类加载器,将使用这些类加载器来查找资源 (仅当查找classpath类资源有效)
* @return
*/
public final static List<URL> getResources(String name, boolean dironly, ClassLoader... loaders) {
if (name.startsWith("classpath*:")) {
name = name.substring(11);
} else if (name.startsWith("classpath:")) {
name = name.substring(10);
} else if (name.startsWith("file:")) {
name = name.substring(5);
URL u = LOADER_USERDIR.getResource(name);
if (u != null) {
return Arrays.asList(u);
} else {
return Arrays.asList();
}
} else if (name.indexOf("://") > -1) {
try {
URL u = new URL(name);
return Arrays.asList(u);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(name + " is invalid! " + e.getMessage());
}
}
if (loaders.length == 0) {
return dironly ? LOADER_DirOnlyCp.getResources(name) : LOADER_DefaultCp.getResources(name);
}
ResourceLoader loader = new ClasspathLoader(dironly, loaders);
return loader.getResources(name);
}
/**
* 将指定文件的资源加载为Properties。注意会查找所有classpath上的多个资源,全部加载。
* 如果资源文件中的相同的项目,后加载的会覆盖先加载的。
*
* @param name
* @param charset
* @return
*/
public final static Map<String, String> loadAsProperties(String name, String charset) {
List<URL> res = getResources(name);
Map<String, String> result = new LinkedHashMap<String, String>();
for (URL u : res) {
IOUtils.loadProperties(IOUtils.getReader(u, charset), result,false);
}
return result;
}
/**
* 将本地文件转化为URL
*/
public static URL fileToURL(File f) {
if (f instanceof URLFile) {
try {
return ((URLFile) f).toURL();
} catch (MalformedURLException e) {
LogUtil.exception(e);
}
}
try {
return f.toURI().toURL();
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
public static InputStream asStream(String string) {
Resource res = getResourceEx(string);
if (res != null)
return res.openStream();
return null;
}
/**
* 查找符合Pattern的所有资源
* @param locationPattern
* @return
* @throws IOException
*/
public static IResource[] findResources(String locationPattern) {
ResourcePatternResolver rl= new PathMatchingResourcePatternResolver();
try {
return rl.getResources(locationPattern);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 查找符合Pattern的所有资源
* @param locationPattern
* @return
* @throws IOException
*/
public static IResource[] findResources(ClassLoader cl,String locationPattern) {
ResourcePatternResolver rl= new PathMatchingResourcePatternResolver(cl);
try {
return rl.getResources(locationPattern);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
*
* @param string
* @return
*/
public static String asString(String string) {
Resource res = getResourceEx(string);
if (res != null)
return res.loadAsString();
return null;
}
/**
* 按Class相同路径的方式查找资源
*
* @param clz
* @param string
* @return
*/
public static String asString(Class<?> clz, String string) {
Resource res = getClassResource(clz, string);
if (res != null)
return res.loadAsString();
return null;
}
/**
* Set the {@link URLConnection#setUseCaches "useCaches"} flag on the given
* connection, preferring {@code false} but leaving the flag at {@code true}
* for JNLP based resources.
*
* @param con
* the URLConnection to set the flag on
*/
public static void useCachesIfNecessary(URLConnection con) {
con.setUseCaches(con.getClass().getSimpleName().startsWith("JNLP"));
}
/**
* Return whether the given resource location is a URL: either a special
* "classpath" pseudo URL or a standard URL.
*
* @param resourceLocation
* the location String to check
* @return whether the location qualifies as a URL
* @see #CLASSPATH_URL_PREFIX
* @see java.net.URL
*/
public static boolean isUrl(String resourceLocation) {
if (resourceLocation == null) {
return false;
}
if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
return true;
}
try {
new URL(resourceLocation);
return true;
} catch (MalformedURLException ex) {
return false;
}
}
/**
* Extract the URL for the actual jar file from the given URL (which may
* point to a resource in a jar file or to a jar file itself).
*
* @param jarUrl
* the original URL
* @return the URL for the actual jar file
* @throws MalformedURLException
* if no valid jar file URL could be extracted
*/
public static URL extractJarFileURL(URL jarUrl) throws MalformedURLException {
String urlFile = jarUrl.getFile();
int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR);
if (separatorIndex != -1) {
String jarFile = urlFile.substring(0, separatorIndex);
try {
return new URL(jarFile);
} catch (MalformedURLException ex) {
// Probably no protocol in original jar URL, like
// "jar:C:/mypath/myjar.jar".
// This usually indicates that the jar file resides in the file
// system.
if (!jarFile.startsWith("/")) {
jarFile = "/" + jarFile;
}
return new URL(FILE_URL_PREFIX + jarFile);
}
} else {
return jarUrl;
}
}
/**
* Determine whether the given URL points to a resource in a jar file, that
* is, has protocol "jar", "zip", "wsjar" or "code-source".
* <p>
* "zip" and "wsjar" are used by BEA WebLogic Server and IBM WebSphere,
* respectively, but can be treated like jar files. The same applies to
* "code-source" URLs on Oracle OC4J, provided that the path contains a jar
* separator.
*
* @param url
* the URL to check
* @return whether the URL has been identified as a JAR URL
*/
public static boolean isJarURL(URL url) {
String protocol = url.getProtocol();
return (URL_PROTOCOL_JAR.equals(protocol) || URL_PROTOCOL_ZIP.equals(protocol) || URL_PROTOCOL_WSJAR.equals(protocol) || (URL_PROTOCOL_CODE_SOURCE.equals(protocol) && url.getPath().contains(JAR_URL_SEPARATOR)));
}
/**
* Create a URI instance for the given URL, replacing spaces with "%20"
* quotes first.
* <p>
* Furthermore, this method works on JDK 1.4 as well, in contrast to the
* {@code URL.toURI()} method.
*
* @param url
* the URL to convert into a URI instance
* @return the URI instance
* @throws URISyntaxException
* if the URL wasn't a valid URI
* @see java.net.URL#toURI()
*/
public static URI toURI(URL url) throws URISyntaxException {
return toURI(url.toString());
}
/**
* Create a URI instance for the given location String, replacing spaces
* with "%20" quotes first.
*
* @param location
* the location String to convert into a URI instance
* @return the URI instance
* @throws URISyntaxException
* if the location wasn't a valid URI
*/
public static URI toURI(String location) throws URISyntaxException {
return new URI(StringUtils.replace(location, " ", "%20"));
}
/**
* Resolve the given resource location to a {@code java.io.File}, i.e. to a
* file in the file system.
* <p>
* Does not check whether the fil actually exists; simply returns the File
* that the given location would correspond to.
*
* @param resourceLocation
* the resource location to resolve: either a "classpath:" pseudo
* URL, a "file:" URL, or a plain file path
* @return a corresponding File object
* @throws FileNotFoundException
* if the resource cannot be resolved to a file in the file
* system
*/
public static File getFile(String resourceLocation) throws FileNotFoundException {
Assert.notNull(resourceLocation, "Resource location must not be null");
if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length());
String description = "class path resource [" + path + "]";
URL url = ClassUtils.getDefaultClassLoader().getResource(path);
if (url == null) {
throw new FileNotFoundException(description + " cannot be resolved to absolute file path " + "because it does not reside in the file system");
}
return getFile(url, description);
}
try {
// try URL
return getFile(new URL(resourceLocation));
} catch (MalformedURLException ex) {
// no URL -> treat as file path
return new File(resourceLocation);
}
}
/**
* Resolve the given resource URL to a {@code java.io.File}, i.e. to a file
* in the file system.
*
* @param resourceUrl
* the resource URL to resolve
* @return a corresponding File object
* @throws FileNotFoundException
* if the URL cannot be resolved to a file in the file system
*/
public static File getFile(URL resourceUrl) throws FileNotFoundException {
return getFile(resourceUrl, "URL");
}
/**
* Resolve the given resource URL to a {@code java.io.File}, i.e. to a file
* in the file system.
*
* @param resourceUrl
* the resource URL to resolve
* @param description
* a description of the original resource that the URL was
* created for (for example, a class path location)
* @return a corresponding File object
* @throws FileNotFoundException
* if the URL cannot be resolved to a file in the file system
*/
public static File getFile(URL resourceUrl, String description) throws FileNotFoundException {
Assert.notNull(resourceUrl, "Resource URL must not be null");
if (!URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol())) {
throw new FileNotFoundException(description + " cannot be resolved to absolute file path " + "because it does not reside in the file system: " + resourceUrl);
}
try {
return new File(toURI(resourceUrl).getSchemeSpecificPart());
} catch (URISyntaxException ex) {
// Fallback for URLs that are not valid URIs (should hardly ever
// happen).
return new File(resourceUrl.getFile());
}
}
/**
* Resolve the given resource URI to a {@code java.io.File}, i.e. to a file
* in the file system.
*
* @param resourceUri
* the resource URI to resolve
* @return a corresponding File object
* @throws FileNotFoundException
* if the URL cannot be resolved to a file in the file system
*/
public static File getFile(URI resourceUri) throws FileNotFoundException {
return getFile(resourceUri, "URI");
}
/**
* Resolve the given resource URI to a {@code java.io.File}, i.e. to a file
* in the file system.
*
* @param resourceUri
* the resource URI to resolve
* @param description
* a description of the original resource that the URI was
* created for (for example, a class path location)
* @return a corresponding File object
* @throws FileNotFoundException
* if the URL cannot be resolved to a file in the file system
*/
public static File getFile(URI resourceUri, String description) throws FileNotFoundException {
Assert.notNull(resourceUri, "Resource URI must not be null");
if (!URL_PROTOCOL_FILE.equals(resourceUri.getScheme())) {
throw new FileNotFoundException(description + " cannot be resolved to absolute file path " + "because it does not reside in the file system: " + resourceUri);
}
return new File(resourceUri.getSchemeSpecificPart());
}
/**
* Determine whether the given URL points to a resource in the file system,
* that is, has protocol "file" or "vfs".
*
* @param url
* the URL to check
* @return whether the URL has been identified as a file system URL
*/
public static boolean isFileURL(URL url) {
String protocol = url.getProtocol();
return (URL_PROTOCOL_FILE.equals(protocol) || protocol.startsWith(URL_PROTOCOL_VFS));
}
}