This commit is contained in:
Looly 2023-10-20 21:25:01 +08:00
parent 7dd8eced41
commit ec966de41c
9 changed files with 414 additions and 85 deletions

View File

@ -23,9 +23,11 @@ import org.dromara.hutool.core.io.file.PathUtil;
import org.dromara.hutool.core.io.resource.Resource;
import org.dromara.hutool.core.io.stream.FastByteArrayOutputStream;
import org.dromara.hutool.core.io.stream.LimitedInputStream;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.net.url.URLUtil;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.ByteUtil;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.util.CharsetUtil;
import org.dromara.hutool.core.util.ObjUtil;
@ -37,6 +39,7 @@ import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.function.Consumer;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
@ -985,6 +988,32 @@ public class ZipUtil {
return fileNames;
}
/**
* 获取对应URL路径的jar文件支持包括file://xxx这类路径<br>
* 来自org.springframework.core.io.support.PathMatchingResourcePatternResolver#getJarFile
*
* @param jarFileUrl jar文件路径
* @return {@link JarFile}
* @throws IORuntimeException IO异常
* @since 6.0.0
*/
public static JarFile ofJar(String jarFileUrl) throws IORuntimeException{
Assert.notBlank(jarFileUrl, "Jar file url is blank!");
if(jarFileUrl.startsWith(URLUtil.FILE_URL_PREFIX)){
try{
jarFileUrl = URLUtil.toURI(jarFileUrl).getSchemeSpecificPart();
} catch (final HutoolException e){
jarFileUrl = jarFileUrl.substring(URLUtil.FILE_URL_PREFIX.length());
}
}
try {
return new JarFile(jarFileUrl);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
// ---------------------------------------------------------------------------------------------- Private method start
/**

View File

@ -0,0 +1,102 @@
/*
* Copyright (c) 2023. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.io.resource;
import org.dromara.hutool.core.compress.ZipUtil;
import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.net.url.URLUtil;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.jar.JarFile;
/**
* Jar包资源对象
*
* @author looly
*/
public class JarResource extends UrlResource {
private static final long serialVersionUID = 1L;
/**
* 构造
*
* @param uri JAR的URI
*/
public JarResource(final URI uri) {
super(uri);
}
/**
* 构造
*
* @param url JAR的URL
*/
public JarResource(final URL url) {
super(url);
}
/**
* 构造
*
* @param url JAR的URL
* @param name 资源名称
*/
public JarResource(final URL url, final String name) {
super(url, name);
}
/**
* 获取URL对应的{@link JarFile}对象
*
* @return {@link JarFile}
* @throws IORuntimeException IO异常
*/
public JarFile getJarFile() throws IORuntimeException {
try {
return doGetJarFile();
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 获取{@link JarFile}<br>
* 首席按通过openConnection方式获取如果得到的不是{@link JarURLConnection}<br>
* 则尝试去除WARJAR等协议分隔符裁剪分隔符前段来直接获取{@link JarFile}
*
* @return {@link JarFile}
* @throws IOException IO异常
*/
private JarFile doGetJarFile() throws IOException {
final URLConnection con = getUrl().openConnection();
if (con instanceof JarURLConnection) {
final JarURLConnection jarCon = (JarURLConnection) con;
return jarCon.getJarFile();
} else {
final String urlFile = getUrl().getFile();
int separatorIndex = urlFile.indexOf(URLUtil.WAR_URL_SEPARATOR);
if (separatorIndex == -1) {
separatorIndex = urlFile.indexOf(URLUtil.JAR_URL_SEPARATOR);
}
if (separatorIndex != -1) {
return ZipUtil.ofJar(urlFile.substring(0, separatorIndex));
} else {
return new JarFile(urlFile);
}
}
}
}

View File

@ -53,9 +53,9 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
* @param resources 资源列表
*/
public MultiResource(final Collection<Resource> resources) {
if(resources instanceof List) {
this.resources = (List<Resource>)resources;
}else {
if (resources instanceof List) {
this.resources = (List<Resource>) resources;
} else {
this.resources = ListUtil.of(resources);
}
}
@ -138,6 +138,7 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
/**
* 增加资源
*
* @param resource 资源
* @return this
*/
@ -146,4 +147,16 @@ public class MultiResource implements Resource, Iterable<Resource>, Iterator<Res
return this;
}
/**
* 增加多个资源
*
* @param iterable 资源列表
* @return this
* @since 6.0.0
*/
public MultiResource addAll(final Iterable<? extends Resource> iterable) {
iterable.forEach((this::add));
return this;
}
}

View File

@ -0,0 +1,167 @@
/*
* Copyright (c) 2023. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.io.resource;
import org.dromara.hutool.core.collection.iter.EnumerationIter;
import org.dromara.hutool.core.compress.ZipUtil;
import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.net.url.URLUtil;
import org.dromara.hutool.core.text.AntPathMatcher;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipException;
public class ResourceFinder {
private final ClassLoader classLoader;
private final AntPathMatcher pathMatcher;
/**
* 构造
*
* @param classLoader 类加载器用于定义查找资源的范围
*/
public ResourceFinder(final ClassLoader classLoader) {
this.classLoader = classLoader;
this.pathMatcher = new AntPathMatcher();
}
/**
* 查找给定表达式对应的资源
*
* @param locationPattern 路径表达式
* @return {@link MultiResource}
*/
public MultiResource find(final String locationPattern) {
// 根目录
final String rootDirPath = determineRootDir(locationPattern);
// 子表达式
final String subPattern = locationPattern.substring(rootDirPath.length());
final MultiResource result = new MultiResource();
// 遍历根目录下所有资源并过滤保留符合条件的资源
for (final Resource rootResource : ResourceUtil.getResources(rootDirPath, classLoader)) {
if (URLUtil.isJarURL(rootResource.getUrl())) {
try {
result.addAll(findInJar(rootResource, subPattern));
} catch (final IOException e) {
throw new IORuntimeException(e);
}
} else {
result.addAll(findInDir(rootResource, subPattern));
}
}
return result;
}
/**
* 查找jar包中的资源
*
* @param rootResource 根资源为jar包文件
* @param subPattern 子表达式 *.xml
* @return 符合条件的资源
* @throws IOException IO异常
*/
protected MultiResource findInJar(final Resource rootResource, final String subPattern) throws IOException {
final URL rootDirURL = rootResource.getUrl();
final URLConnection conn = rootDirURL.openConnection();
final JarFile jarFile;
final String jarFileUrl;
String rootEntryPath;
final boolean closeJarFile;
if (conn instanceof JarURLConnection) {
final JarURLConnection jarCon = (JarURLConnection) conn;
URLUtil.useCachesIfNecessary(jarCon);
jarFile = jarCon.getJarFile();
final JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : StrUtil.EMPTY);
closeJarFile = !jarCon.getUseCaches();
} else {
//
final String urlFile = rootDirURL.getFile();
try {
int separatorIndex = urlFile.indexOf(URLUtil.WAR_URL_SEPARATOR);
if (separatorIndex == -1) {
separatorIndex = urlFile.indexOf(URLUtil.JAR_URL_SEPARATOR);
}
if (separatorIndex != -1) {
jarFileUrl = urlFile.substring(0, separatorIndex);
rootEntryPath = urlFile.substring(separatorIndex + 2); // both separators are 2 chars
jarFile = ZipUtil.ofJar(jarFileUrl);
} else {
jarFile = new JarFile(urlFile);
rootEntryPath = StrUtil.EMPTY;
}
closeJarFile = true;
} catch (final ZipException ex) {
return new MultiResource();
}
}
rootEntryPath = StrUtil.addSuffixIfNot(rootEntryPath, StrUtil.SLASH);
// 遍历jar中的entry筛选之
final MultiResource result = new MultiResource();
try {
String entryPath;
for (final JarEntry entry : new EnumerationIter<>(jarFile.entries())) {
entryPath = entry.getName();
if (entryPath.startsWith(rootEntryPath)) {
final String relativePath = entryPath.substring(rootEntryPath.length());
if (pathMatcher.match(subPattern, relativePath)) {
result.add(new UrlResource(URLUtil.getURL(rootDirURL, relativePath)));
}
}
}
} finally {
if (closeJarFile) {
IoUtil.closeQuietly(jarFile);
}
}
return result;
}
protected MultiResource findInDir(final Resource rootResource, final String subPattern) {
}
/**
* 根据给定的路径表达式找到跟路径<br>
* 根路径即不包含表达式的路径 "/WEB-INF/*.xml" 返回 "/WEB-INF/"
*
* @param location 路径表达式
*/
protected String determineRootDir(final String location) {
final int prefixEnd = location.indexOf(':') + 1;
int rootDirEnd = location.length();
while (rootDirEnd > prefixEnd && pathMatcher.isPattern(location.substring(prefixEnd, rootDirEnd))) {
rootDirEnd = location.lastIndexOf(CharUtil.SLASH, rootDirEnd - 2) + 1;
}
if (rootDirEnd == 0) {
rootDirEnd = prefixEnd;
}
return location.substring(0, rootDirEnd);
}
}

View File

@ -12,22 +12,23 @@
package org.dromara.hutool.core.io.resource;
import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.io.file.FileNameUtil;
import org.dromara.hutool.core.net.NetUtil;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.net.url.URLUtil;
import org.dromara.hutool.core.util.ObjUtil;
import java.io.*;
import java.net.*;
import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URL;
/**
* URL资源访问类
* @author Looly
*
* @author Looly
*/
public class UrlResource implements Resource, Serializable{
public class UrlResource implements Resource, Serializable {
private static final long serialVersionUID = 1L;
protected URL url;
@ -35,8 +36,10 @@ public class UrlResource implements Resource, Serializable{
protected String name;
//-------------------------------------------------------------------------------------- Constructor start
/**
* 构造
*
* @param uri URI
* @since 5.7.21
*/
@ -46,6 +49,7 @@ public class UrlResource implements Resource, Serializable{
/**
* 构造
*
* @param url URL
*/
public UrlResource(final URL url) {
@ -54,12 +58,13 @@ public class UrlResource implements Resource, Serializable{
/**
* 构造
* @param url URL允许为空
*
* @param url URL允许为空
* @param name 资源名称
*/
public UrlResource(final URL url, final String name) {
this.url = url;
if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){
if (null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())) {
this.lastModified = FileUtil.file(url).lastModified();
}
this.name = ObjUtil.defaultIfNull(name, () -> (null != url ? FileNameUtil.getName(url.getPath()) : null));
@ -73,7 +78,7 @@ public class UrlResource implements Resource, Serializable{
}
@Override
public URL getUrl(){
public URL getUrl() {
return this.url;
}
@ -83,8 +88,8 @@ public class UrlResource implements Resource, Serializable{
}
@Override
public InputStream getStream() throws NoResourceException{
if(null == this.url){
public InputStream getStream() throws NoResourceException {
if (null == this.url) {
throw new NoResourceException("Resource URL is null!");
}
return URLUtil.getStream(url);
@ -98,18 +103,31 @@ public class UrlResource implements Resource, Serializable{
/**
* 获得File
*
* @return {@link File}
*/
public File getFile(){
public File getFile() {
return FileUtil.file(this.url);
}
/**
* 返回路径
*
* @return 返回URL路径
*/
@Override
public String toString() {
return (null == this.url) ? "null" : this.url.toString();
}
/**
* 获取相对于本资源的资源
*
* @param relativePath 相对路径
* @return 子资源
* @since 6.0.0
*/
public UrlResource createRelative(final String relativePath) {
return new UrlResource(URLUtil.getURL(getUrl(), relativePath));
}
}

View File

@ -16,6 +16,7 @@ import org.dromara.hutool.core.classloader.ClassLoaderUtil;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
@ -37,6 +38,7 @@ public class VfsResource implements Resource {
private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED;
private static final Method VIRTUAL_FILE_METHOD_TO_URL;
private static final Method VIRTUAL_FILE_METHOD_GET_NAME;
private static final Method VIRTUAL_FILE_METHOD_GET_PHYSICAL_FILE;
static {
final Class<?> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile");
@ -47,6 +49,7 @@ public class VfsResource implements Resource {
VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified");
VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL");
VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName");
VIRTUAL_FILE_METHOD_GET_PHYSICAL_FILE = virtualFile.getMethod("getPhysicalFile");
} catch (final NoSuchMethodException ex) {
throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex);
}
@ -117,4 +120,14 @@ public class VfsResource implements Resource {
return MethodUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE);
}
/**
* 获取物理文件对象
*
* @return 物理文件对象
* @since 6.0.0
*/
public File getFile(){
return MethodUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_PHYSICAL_FILE);
}
}

View File

@ -20,7 +20,6 @@ import org.dromara.hutool.core.io.file.FileNameUtil;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.io.resource.ResourceUtil;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.net.NetUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.CharsetUtil;
@ -28,17 +27,9 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.*;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.jar.JarFile;
/**
* URLUniform Resource Locator统一资源定位符相关工具类
@ -52,10 +43,11 @@ import java.util.jar.JarFile;
* protocol :// hostname[:port] / path / [:parameters][?query]#fragment
* </pre>
*
* @author xiaoleilu
* @author looly
*/
public class URLUtil {
// region const
/**
* 针对ClassPath路径的伪协议前缀兼容Spring: "classpath:"
*/
@ -108,6 +100,7 @@ public class URLUtil {
* WAR路径及内部文件路径分界符
*/
public static final String WAR_URL_SEPARATOR = "*/";
// endregion
/**
* {@link URI}转换为{@link URL}
@ -215,6 +208,8 @@ public class URLUtil {
}
}
// region getURL
/**
* 获得URL
*
@ -243,14 +238,34 @@ public class URLUtil {
*
* @param file URL对应的文件对象
* @return URL
* @throws HutoolException MalformedURLException
* @throws IORuntimeException URL格式错误
*/
public static URL getURL(final File file) {
Assert.notNull(file, "File is null !");
try {
return file.toURI().toURL();
} catch (final MalformedURLException e) {
throw new HutoolException(e, "Error occured when get URL!");
throw new IORuntimeException(e, "Error occurred when get URL!");
}
}
/**
* 获取相对于给定URL的新的URL<br>
* 来自org.springframework.core.io.UrlResource#createRelativeURL
*
* @param url 基础URL
* @param relativePath 相对路径
* @return 相对于URL的子路径URL
* @throws IORuntimeException URL格式错误
* @since 6.0.0
*/
public static URL getURL(final URL url, String relativePath) throws HutoolException {
// # 在文件路径中合法但是在URL中非法此处转义
relativePath = StrUtil.replace(StrUtil.removePrefix(relativePath, StrUtil.SLASH), "#", "%23");
try {
return new URL(url, relativePath);
} catch (final MalformedURLException e) {
throw new IORuntimeException(e, "Error occurred when get URL!");
}
}
@ -259,7 +274,7 @@ public class URLUtil {
*
* @param files URL对应的文件对象
* @return URL
* @throws HutoolException MalformedURLException
* @throws IORuntimeException URL格式错误
*/
public static URL[] getURLs(final File... files) {
final URL[] urls = new URL[files.length];
@ -268,11 +283,12 @@ public class URLUtil {
urls[i] = files[i].toURI().toURL();
}
} catch (final MalformedURLException e) {
throw new HutoolException(e, "Error occured when get URL!");
throw new IORuntimeException(e, "Error occurred when get URL!");
}
return urls;
}
// endregion
/**
* 获取URL中域名部分只保留URL中的协议ProtocolHost其它为null
@ -351,6 +367,8 @@ public class URLUtil {
return (null != path) ? path : url.getPath();
}
// region toURI
/**
* 转URL为URI
*
@ -409,6 +427,7 @@ public class URLUtil {
throw new HutoolException(e);
}
}
// endregion
/**
* 提供的URL是否为文件<br>
@ -482,21 +501,7 @@ public class URLUtil {
return IoUtil.toReader(getStream(url), charset);
}
/**
* 从URL中获取JarFile
*
* @param url URL
* @return JarFile
* @since 4.1.5
*/
public static JarFile getJarFile(final URL url) {
try {
final JarURLConnection urlConnection = (JarURLConnection) url.openConnection();
return urlConnection.getJarFile();
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
// region normalize
/**
* 标准化URL字符串包括
@ -593,6 +598,7 @@ public class URLUtil {
}
return protocol + domain + StrUtil.emptyIfNull(path) + StrUtil.emptyIfNull(params);
}
// endregion
/**
* 将Map形式的Form表单数据转换为Url参数形式<br>
@ -611,31 +617,7 @@ public class URLUtil {
return UrlQuery.of(paramMap).build(charset);
}
/**
* 获取指定URL对应资源的内容长度对于Http其长度使用Content-Length头决定
*
* @param url URL
* @return 内容长度未知返回-1
* @throws IORuntimeException IO异常
* @since 5.3.4
*/
public static long getContentLength(final URL url) throws IORuntimeException {
if (null == url) {
return -1;
}
URLConnection conn = null;
try {
conn = url.openConnection();
return conn.getContentLengthLong();
} catch (final IOException e) {
throw new IORuntimeException(e);
} finally {
if (conn instanceof HttpURLConnection) {
((HttpURLConnection) conn).disconnect();
}
}
}
// region getDataUri
/**
* Data URI Scheme封装数据格式为Base64data URI scheme 允许我们使用内联inline-code的方式在网页中包含数据<br>
@ -708,6 +690,7 @@ public class URLUtil {
return builder.toString();
}
// endregion
/**
* 获取URL对应数据长度
@ -732,16 +715,21 @@ public class URLUtil {
} else {
// 如果资源打在jar包中或来自网络使用网络请求长度
// issue#3226, 来自Spring的AbstractFileResolvingResource
URLConnection conn = null;
try {
final URLConnection con = url.openConnection();
useCachesIfNecessary(con);
if (con instanceof HttpURLConnection) {
final HttpURLConnection httpCon = (HttpURLConnection) con;
conn = url.openConnection();
useCachesIfNecessary(conn);
if (conn instanceof HttpURLConnection) {
final HttpURLConnection httpCon = (HttpURLConnection) conn;
httpCon.setRequestMethod("HEAD");
}
return con.getContentLengthLong();
return conn.getContentLengthLong();
} catch (final IOException e) {
throw new IORuntimeException(e);
} finally {
if (conn instanceof HttpURLConnection) {
((HttpURLConnection) conn).disconnect();
}
}
}
}

View File

@ -16,14 +16,13 @@ import org.dromara.hutool.core.classloader.ClassLoaderUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.iter.EnumerationIter;
import org.dromara.hutool.core.exception.ExceptionUtil;
import org.dromara.hutool.core.exception.HutoolException;
import org.dromara.hutool.core.io.IORuntimeException;
import org.dromara.hutool.core.io.file.FileNameUtil;
import org.dromara.hutool.core.io.resource.JarResource;
import org.dromara.hutool.core.io.resource.ResourceUtil;
import org.dromara.hutool.core.net.url.URLDecoder;
import org.dromara.hutool.core.net.url.URLUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.CharsetUtil;
import org.dromara.hutool.core.util.SystemUtil;
@ -268,7 +267,7 @@ public class ClassScanner implements Serializable {
scanFile(new File(URLDecoder.decode(url.getFile(), this.charset)), null);
break;
case "jar":
scanJar(URLUtil.getJarFile(url));
scanJar(new JarResource(url).getJarFile());
break;
}
}

View File

@ -350,7 +350,7 @@ public class AntPathMatcher {
pos += skipped;
skipped = skipSegment(path, pos, pattDir);
if (skipped < pattDir.length()) {
return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0))));
return (skipped > 0 || (!pattDir.isEmpty() && isWildcardChar(pattDir.charAt(0))));
}
pos += skipped;
}
@ -657,9 +657,9 @@ public class AntPathMatcher {
/**
* Tests whether or not a string matches against a pattern via a {@link Pattern}.
* Tests whether a string matches against a pattern via a {@link Pattern}.
* <p>The pattern may contain special characters: '*' means zero or more characters; '?' means one and
* only one character; '{' and '}' indicate a URI template pattern. For example <tt>/users/{user}</tt>.
* only one character; '{' and '}' indicate a URI template pattern. For example {@code /users/{user}}.
*/
protected static class AntPathStringMatcher {