io.microsphere.net.URLUtils Maven / Gradle / Ivy
The newest version!
/**
*
*/
package io.microsphere.net;
import io.microsphere.collection.MapUtils;
import io.microsphere.util.ClassPathUtils;
import io.microsphere.util.jar.JarUtils;
import javax.annotation.Nonnull;
import java.io.File;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import static io.microsphere.collection.CollectionUtils.size;
import static io.microsphere.constants.PathConstants.BACK_SLASH;
import static io.microsphere.constants.PathConstants.DOUBLE_SLASH;
import static io.microsphere.constants.PathConstants.SLASH;
import static io.microsphere.constants.ProtocolConstants.EAR_PROTOCOL;
import static io.microsphere.constants.ProtocolConstants.FILE_PROTOCOL;
import static io.microsphere.constants.ProtocolConstants.JAR_PROTOCOL;
import static io.microsphere.constants.ProtocolConstants.WAR_PROTOCOL;
import static io.microsphere.constants.SeparatorConstants.ARCHIVE_ENTRY_SEPARATOR;
import static io.microsphere.constants.SymbolConstants.AND_CHAR;
import static io.microsphere.constants.SymbolConstants.COLON;
import static io.microsphere.constants.SymbolConstants.COLON_CHAR;
import static io.microsphere.constants.SymbolConstants.EQUAL_CHAR;
import static io.microsphere.constants.SymbolConstants.QUERY_STRING;
import static io.microsphere.constants.SymbolConstants.QUERY_STRING_CHAR;
import static io.microsphere.constants.SymbolConstants.SEMICOLON_CHAR;
import static io.microsphere.constants.SymbolConstants.SHARP_CHAR;
import static io.microsphere.reflect.FieldUtils.getStaticFieldValue;
import static io.microsphere.reflect.FieldUtils.setStaticFieldValue;
import static io.microsphere.util.StringUtils.EMPTY;
import static io.microsphere.util.StringUtils.EMPTY_STRING_ARRAY;
import static io.microsphere.util.StringUtils.isBlank;
import static io.microsphere.util.StringUtils.length;
import static io.microsphere.util.StringUtils.replace;
import static io.microsphere.util.StringUtils.split;
import static io.microsphere.util.StringUtils.substringAfterLast;
import static io.microsphere.util.SystemUtils.FILE_ENCODING;
import static java.lang.reflect.Array.getLength;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
/**
* {@link URL} Utility class
*
* @author Mercy
* @version 1.0.0
* @see URL
* @see URLEncoder
* @see URLDecoder
* @since 1.0.0
*/
public abstract class URLUtils {
/**
* The default encoding : "UTF-8"
*/
public static final String DEFAULT_ENCODING = FILE_ENCODING;
/**
* The empty array of {@link URL}
*/
public static final URL[] EMPTY_URL_ARRAY = new URL[0];
/**
* The length of {@link #ARCHIVE_ENTRY_SEPARATOR_LENGTH}
*/
private static final int ARCHIVE_ENTRY_SEPARATOR_LENGTH = ARCHIVE_ENTRY_SEPARATOR.length();
/**
* The property which specifies the package prefix list to be scanned
* for protocol handlers. The value of this property (if any) should
* be a vertical bar delimited list of package names to search through
* for a protocol handler to load. The policy of this class is that
* all protocol handlers will be in a class called .Handler,
* and each package in the list is examined in turn for a matching
* handler. If none are found (or the property is not specified), the
* default package prefix, sun.net.www.protocol, is used. The search
* proceeds from the first package in the list to the last and stops
* when a match is found.
*
* @see {@link URL#protocolPathProp}
*/
public static final String HANDLER_PACKAGES_PROPERTY_NAME = "java.protocol.handler.pkgs";
/**
* The prefix of package for {@link URLStreamHandler Handlers}
*/
public static final String DEFAULT_HANDLER_PACKAGE_PREFIX = "sun.net.www.protocol";
/**
* The separator character of {@link URLStreamHandler Handlers'} packages.
*/
public static final char HANDLER_PACKAGES_SEPARATOR_CHAR = '|';
/**
* The convention class name of {@link URLStreamHandler Handler}.
*/
public static final String HANDLER_CONVENTION_CLASS_NAME = "Handler";
/**
* The matrix name for the URLs' sub-protocol
*/
public static final String SUB_PROTOCOL_MATRIX_NAME = "_sp";
/**
* Resolve the entry path from Archive File URL
*
* @param archiveFileURL Archive File URL
* @return Relative path in archive
* @throws NullPointerException archiveFileURL
is null
*/
public static String resolveArchiveEntryPath(URL archiveFileURL) throws NullPointerException {
// NPE check
return doResolveArchiveEntryPath(archiveFileURL.getPath());
}
protected static String doResolveArchiveEntryPath(String path) {
int beginIndex = indexOfArchiveEntry(path);
if (beginIndex > -1) {
String relativePath = path.substring(beginIndex + ARCHIVE_ENTRY_SEPARATOR_LENGTH);
return decode(relativePath);
}
return null;
}
/**
* Resolve base path from the specified URL
*
* @param url the specified URL
* @return base path
* @throws NullPointerException if url
is null
*/
public static String resolveBasePath(URL url) throws NullPointerException {
// NPE check
return doResolveBasePath(url.getPath());
}
protected static String doResolveBasePath(String path) {
int beginIndex = path.lastIndexOf(COLON_CHAR);
if (beginIndex == -1) {
return path;
}
beginIndex += 1;
int endIndex = indexOfArchiveEntry(path);
if (endIndex == -1) {
return path.substring(beginIndex);
} else {
return path.substring(beginIndex, endIndex);
}
}
/**
* Resolve archive file
*
* @param resourceURL the URL of resource
* @return Resolve archive file If exists
* @throws NullPointerException
*/
public static File resolveArchiveFile(URL resourceURL) throws NullPointerException {
String protocol = resourceURL.getProtocol();
if (FILE_PROTOCOL.equals(protocol)) {
return resolveArchiveDirectory(resourceURL);
} else {
return doResolveArchiveFile(resourceURL);
}
}
protected static File doResolveArchiveFile(URL url) throws NullPointerException {
if (isArchiveURL(url)) {
String basePath = resolveBasePath(url);
File archiveFile = new File(basePath);
if (archiveFile.exists()) {
return archiveFile;
}
}
return null;
}
protected static File resolveArchiveDirectory(URL resourceURL) {
String resourcePath = new File(resourceURL.getFile()).toString();
Set classPaths = ClassPathUtils.getClassPaths();
File archiveDirectory = null;
for (String classPath : classPaths) {
if (resourcePath.contains(classPath)) {
archiveDirectory = new File(classPath);
break;
}
}
return archiveDirectory;
}
/**
* Resolve the query parameters {@link Map} from specified URL,The parameter name as key ,parameter value list as key
*
* @param url URL
* @return Non-null and Read-only {@link Map} , the order of parameters is determined by query string
*/
@Nonnull
public static Map> resolveQueryParameters(String url) {
String queryString = substringAfterLast(url, QUERY_STRING);
return resolveParameters(queryString, AND_CHAR);
}
/**
* Resolve the matrix parameters {@link Map} from specified URL,The parameter name as key ,parameter value list as key
*
* @param url URL
* @return Non-null and Read-only {@link Map} , the order of parameters is determined by matrix string
*/
@Nonnull
public static Map> resolveMatrixParameters(String url) {
int startIndex = url.indexOf(SEMICOLON_CHAR);
if (startIndex == -1) { // The matrix separator ";" was not found
return emptyMap();
}
int endIndex = url.indexOf(QUERY_STRING_CHAR);
if (endIndex == -1) { // The query string separator "?" was not found
endIndex = url.indexOf(SHARP_CHAR);
}
if (endIndex == -1) { // The fragment separator "#" was not found
endIndex = url.length();
}
String matrixString = url.substring(startIndex, endIndex);
return resolveParameters(matrixString, SEMICOLON_CHAR);
}
/**
* Normalize Path(maybe from File or URL), will remove duplicated slash or backslash from path. For example,
*
* resolvePath("C:\\Windows\\\\temp") == "C:/Windows/temp"; resolvePath("C:\\\\\Windows\\/temp") ==
* "C:/Windows/temp"; resolvePath("/home/////index.html") == "/home/index.html";
*
* @param path Path
* @return a newly resolved path
*/
public static String normalizePath(final String path) {
if (isBlank(path)) {
return path;
}
String resolvedPath = path.trim();
while (resolvedPath.contains(BACK_SLASH)) {
resolvedPath = replace(resolvedPath, BACK_SLASH, SLASH);
}
while (resolvedPath.contains(DOUBLE_SLASH)) {
resolvedPath = replace(resolvedPath, DOUBLE_SLASH, SLASH);
}
return resolvedPath;
}
/**
* Translates a string into application/x-www-form-urlencoded
format using a specific encoding scheme.
* This method uses the supplied encoding scheme to obtain the bytes for unsafe characters.
*
* Note: The World
* Wide Web Consortium Recommendation states that UTF-8 should be used. Not doing so may introduce
* incompatibilites.
*
* @param value String
to be translated.
* @param encoding The name of a supported character encoding.
* @return the translated String
.
* @throws IllegalArgumentException If the named encoding is not supported
* @see URLDecoder#decode(String, String)
*/
public static String encode(String value, String encoding) throws IllegalArgumentException {
String encodedValue = null;
try {
encodedValue = URLEncoder.encode(value, encoding);
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage());
}
return encodedValue;
}
/**
* {@link #encode(String, String)} with "UTF-8" encoding
*
* @param value the String
to decode
* @return the newly encoded String
*/
public static String encode(String value) {
return encode(value, DEFAULT_ENCODING);
}
/**
* {@link #decode(String, String)} with "UTF-8" encoding
*
* @param value the String
to decode
* @return the newly decoded String
*/
public static String decode(String value) {
return decode(value, DEFAULT_ENCODING);
}
/**
* Decodes a application/x-www-form-urlencoded
string using a specific encoding scheme. The supplied
* encoding is used to determine what characters are represented by any consecutive sequences of the form
* "%xy
".
*
* Note: The World
* Wide Web Consortium Recommendation states that UTF-8 should be used. Not doing so may introduce
* incompatibilites.
*
* @param value the String
to decode
* @param encoding The name of a supported encoding
* @return the newly decoded String
* @throws IllegalArgumentException If character encoding needs to be consulted, but named character encoding is not supported
*/
public static String decode(String value, String encoding) throws IllegalArgumentException {
String decodedValue = null;
try {
decodedValue = URLDecoder.decode(value, encoding);
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage());
}
return decodedValue;
}
/**
* Is directory URL?
*
* @param url URL
* @return if directory , return true
*/
public static boolean isDirectoryURL(URL url) {
boolean isDirectory = false;
if (url != null) {
String protocol = url.getProtocol();
try {
if (JAR_PROTOCOL.equals(protocol)) {
JarFile jarFile = JarUtils.toJarFile(url); // Test whether valid jar or not
final String relativePath = JarUtils.resolveRelativePath(url);
if (EMPTY.equals(relativePath)) { // root directory in jar
isDirectory = true;
} else {
JarEntry jarEntry = jarFile.getJarEntry(relativePath);
isDirectory = jarEntry != null && jarEntry.isDirectory();
}
} else if (FILE_PROTOCOL.equals(protocol)) {
File classPathFile = new File(url.toURI());
isDirectory = classPathFile.isDirectory();
}
} catch (Exception e) {
isDirectory = false;
}
}
return isDirectory;
}
/**
* Is Jar URL?
*
* @param url URL
* @return If jar , return true
*/
public static boolean isJarURL(URL url) {
String protocol = url.getProtocol();
boolean flag = false;
if (FILE_PROTOCOL.equals(protocol)) {
try {
File file = new File(url.toURI());
JarFile jarFile = new JarFile(file);
flag = jarFile != null;
} catch (Exception e) {
}
} else if (JAR_PROTOCOL.equals(protocol)) {
flag = true;
}
return flag;
}
/**
* Is an archive URL?
*
* @param url URL
* @return If an archive , return true
*/
public static boolean isArchiveURL(URL url) {
String protocol = url.getProtocol();
boolean flag = false;
switch (protocol) {
case JAR_PROTOCOL:
case WAR_PROTOCOL:
case EAR_PROTOCOL:
flag = true;
break;
case FILE_PROTOCOL:
try {
File file = new File(url.toURI());
JarFile jarFile = new JarFile(file);
flag = jarFile != null;
} catch (Exception e) {
}
}
return flag;
}
/**
* Build multiple paths to URI
*
* @param paths multiple paths
* @return URI
*/
public static String buildURI(String... paths) {
int length = getLength(paths);
if (length < 1) {
return SLASH;
}
StringBuilder uriBuilder = new StringBuilder(SLASH);
for (int i = 0; i < length; i++) {
String path = paths[i];
uriBuilder.append(path);
if (i < length - 1) {
uriBuilder.append(SLASH);
}
}
return normalizePath(uriBuilder.toString());
}
/**
* Build the Matrix String
*
* @param matrixParameters the {@link Map} of matrix parameters
* @return the Matrix String
*/
public static String buildMatrixString(Map> matrixParameters) {
if (MapUtils.isEmpty(matrixParameters)) {
return null;
}
StringBuilder matrixStringBuilder = new StringBuilder();
for (Map.Entry> entry : matrixParameters.entrySet()) {
String name = entry.getKey();
if (SUB_PROTOCOL_MATRIX_NAME.equals(name)) {
continue;
}
List values = entry.getValue();
matrixStringBuilder.append(buildMatrixString(name, values.toArray(EMPTY_STRING_ARRAY)));
}
return matrixStringBuilder.toString();
}
/**
* Build the Matrix String
*
* @param name the name of matrix parameter
* @param values the values of matrix parameter
* @return the Matrix String
*/
public static String buildMatrixString(String name, String... values) {
return buildString(name, values, SEMICOLON_CHAR, EQUAL_CHAR);
}
/**
* Converts a URL of a specific protocol to a String.
*
* @param url {@link URL}
* @return non-null
* @throws NullPointerException If url
is null
*/
public static String toString(URL url) throws NullPointerException {
return toExternalForm(url);
}
/**
* Converts a URL of a specific protocol to a String.
*
* @param url {@link URL}
* @return non-null
* @throws NullPointerException If url
is null
*/
public static String toExternalForm(URL url) throws NullPointerException {
// pre-compute length of StringBuilder
String protocol = url.getProtocol();
String authority = url.getAuthority();
String path = url.getPath();
String query = url.getQuery();
String ref = url.getRef();
String matrix = null;
int authorityLen = length(authority);
int pathLen = length(path);
int queryLen = length(query);
int refLen = length(ref);
boolean hasAuthority = authorityLen > 0;
boolean hasPath = pathLen > 0;
boolean hasQuery = queryLen > 0;
boolean hasRef = refLen > 0;
boolean hasMatrix = false;
int len = 1;
if (hasAuthority) {
protocol = reformProtocol(protocol, authority);
authority = resolveAuthority(authority);
len += 2 + authority.length();
}
if (hasPath) {
int indexOfMatrixString = indexOfMatrixString(path);
if (indexOfMatrixString > -1) {
hasMatrix = true;
Map> matrixParameters = resolveMatrixParameters(path);
List subProtocols = matrixParameters.getOrDefault(SUB_PROTOCOL_MATRIX_NAME, emptyList());
protocol = reformProtocol(protocol, subProtocols);
matrix = buildMatrixString(matrixParameters);
path = resolvePath(path, indexOfMatrixString);
}
}
len += length(path);
len += length(protocol);
if (hasQuery) {
len += 1 + queryLen;
}
if (hasRef) {
len += 1 + refLen;
}
if (hasMatrix) {
len += length(matrix);
}
StringBuilder result = new StringBuilder(len);
result.append(protocol);
result.append(COLON_CHAR);
if (hasAuthority) {
result.append(DOUBLE_SLASH);
result.append(authority);
}
if (hasPath) {
result.append(path);
}
if (hasMatrix) {
result.append(matrix);
}
if (hasQuery) {
result.append(QUERY_STRING_CHAR);
result.append(query);
}
if (hasRef) {
result.append(SHARP_CHAR);
result.append(ref);
}
return result.toString();
}
public static String getSubProtocol(String url) {
Map> parameters = resolveMatrixParameters(url);
return getFirst(parameters, SUB_PROTOCOL_MATRIX_NAME);
}
public static List resolveSubProtocols(URL url) {
return resolveSubProtocols(url.toString());
}
public static List resolveSubProtocols(String url) {
String subProtocolsString = findSubProtocolsString(url);
final List subProtocols;
if (subProtocolsString == null) {
Map> parameters = resolveMatrixParameters(url);
subProtocols = parameters.get(SUB_PROTOCOL_MATRIX_NAME);
} else {
String[] values = split(subProtocolsString, COLON_CHAR);
subProtocols = Arrays.asList(values);
}
return subProtocols == null ? emptyList() : unmodifiableList(subProtocols);
}
private static String findSubProtocolsString(String url) {
int startIndex = url.indexOf(COLON_CHAR);
if (startIndex > -1) {
int endIndex = url.indexOf("://", startIndex);
if (endIndex > startIndex) {
return url.substring(startIndex, endIndex);
}
}
return null;
}
public static String resolveAuthority(URL url) {
return resolveAuthority(url.getAuthority());
}
public static String resolveAuthority(String authority) {
return truncateMatrixString(authority);
}
public static String resolvePath(URL url) {
return resolvePath(url.getPath());
}
public static String resolvePath(String value) {
int indexOfMatrixString = indexOfMatrixString(value);
return resolvePath(value, indexOfMatrixString);
}
public static String resolvePath(String value, int indexOfMatrixString) {
return indexOfMatrixString > -1 ? value.substring(0, indexOfMatrixString) : value;
}
protected static String truncateMatrixString(String value) {
int lastIndex = indexOfMatrixString(value);
return lastIndex > -1 ? value.substring(0, lastIndex) : value;
}
protected static int indexOfMatrixString(String value) {
return value == null ? -1 : value.indexOf(SEMICOLON_CHAR);
}
/**
* Set the specified {@link URLStreamHandlerFactory} for {@link URL URL's} if not set before, otherwise,
* add it into {@link CompositeURLStreamHandlerFactory} that will be set.
*
* @param factory {@link URLStreamHandlerFactory}
*/
public static void attachURLStreamHandlerFactory(URLStreamHandlerFactory factory) {
if (factory == null) {
return;
}
URLStreamHandlerFactory oldFactory = getURLStreamHandlerFactory();
CompositeURLStreamHandlerFactory compositeFactory;
if (oldFactory == null) { // old factory is absent
URL.setURLStreamHandlerFactory(factory);
return;
} else if (oldFactory instanceof CompositeURLStreamHandlerFactory) {
compositeFactory = (CompositeURLStreamHandlerFactory) oldFactory;
} else {
compositeFactory = new CompositeURLStreamHandlerFactory();
// Add the old one
compositeFactory.addURLStreamHandlerFactory(oldFactory);
// clear the compositeFactory to ensure the invocation of URL.setURLStreamHandlerFactory successfully
clearURLStreamHandlerFactory();
URL.setURLStreamHandlerFactory(compositeFactory);
}
// Add the new one
compositeFactory.addURLStreamHandlerFactory(factory);
}
public static URLStreamHandlerFactory getURLStreamHandlerFactory() {
return getStaticFieldValue(URL.class, "factory");
}
/**
* Register an instance of {@link ExtendableProtocolURLStreamHandler}
*
* @param handler {@link ExtendableProtocolURLStreamHandler}
*/
public static void registerURLStreamHandler(ExtendableProtocolURLStreamHandler handler) {
registerURLStreamHandler(handler.getProtocol(), handler);
}
/**
* Register an instance of {@link URLStreamHandler} with the specified protocol
*
* @param protocol the specified protocol of {@link URL}
* @param handler {@link URLStreamHandler}
*/
public static void registerURLStreamHandler(String protocol, URLStreamHandler handler) {
MutableURLStreamHandlerFactory factory = getMutableURLStreamHandlerFactory(true);
factory.addURLStreamHandler(protocol, handler);
attachURLStreamHandlerFactory(factory);
}
protected static MutableURLStreamHandlerFactory getMutableURLStreamHandlerFactory() {
return getMutableURLStreamHandlerFactory(false);
}
protected static MutableURLStreamHandlerFactory getMutableURLStreamHandlerFactory(boolean createIfAbsent) {
URLStreamHandlerFactory oldFactory = getURLStreamHandlerFactory();
MutableURLStreamHandlerFactory factory = findMutableURLStreamHandlerFactory(oldFactory);
if (oldFactory instanceof CompositeURLStreamHandlerFactory) {
factory = findMutableURLStreamHandlerFactory((CompositeURLStreamHandlerFactory) oldFactory);
}
if (factory == null && createIfAbsent) {
factory = new MutableURLStreamHandlerFactory();
}
return factory;
}
private static MutableURLStreamHandlerFactory findMutableURLStreamHandlerFactory(CompositeURLStreamHandlerFactory compositeFactory) {
MutableURLStreamHandlerFactory target = null;
for (URLStreamHandlerFactory factory : compositeFactory.getFactories()) {
target = findMutableURLStreamHandlerFactory(factory);
if (target != null) {
break;
}
}
return target;
}
private static MutableURLStreamHandlerFactory findMutableURLStreamHandlerFactory(URLStreamHandlerFactory factory) {
if (factory instanceof MutableURLStreamHandlerFactory) {
return (MutableURLStreamHandlerFactory) factory;
}
return null;
}
protected static void clearURLStreamHandlerFactory() {
setStaticFieldValue(URL.class, "factory", null);
}
protected static String reformProtocol(String protocol, String spec) {
List subProtocols = resolveSubProtocols(spec);
return reformProtocol(protocol, subProtocols);
}
protected static String reformProtocol(String protocol, List subProtocols) {
int size = size(subProtocols);
if (size < 1) { // the matrix of sub-protocols was not found
return protocol;
}
StringJoiner protocolJoiner = new StringJoiner(COLON);
protocolJoiner.add(protocol);
for (int i = 0; i < size; i++) {
protocolJoiner.add(subProtocols.get(i));
}
return protocolJoiner.toString();
}
protected static Map> resolveParameters(String paramsString, char separatorChar) {
String[] params = split(paramsString, separatorChar);
int paramsLen = params == null ? 0 : params.length;
if (paramsLen == 0) {
return emptyMap();
}
Map> parametersMap = new LinkedHashMap(paramsLen, Float.MIN_NORMAL);
for (int i = 0; i < paramsLen; i++) {
String param = params[i];
String[] nameAndValue = split(param, EQUAL_CHAR);
int len = nameAndValue.length;
if (len > 0) {
String name = nameAndValue[0];
String value = len > 1 ? nameAndValue[1] : EMPTY;
List paramValueList = parametersMap.get(name);
if (paramValueList == null) {
paramValueList = new LinkedList();
parametersMap.put(name, paramValueList);
}
paramValueList.add(value);
}
}
return unmodifiableMap(parametersMap);
}
protected static String buildString(String name, String[] values, char separator, char joiner) {
int len = getLength(values);
if (len == 0) {
return null;
}
// 2 = length(separator) + length(joiner)
int capacity = 2 * len + name.length();
for (int i = 0; i < len; i++) {
capacity += length(values[i]);
}
StringBuilder stringBuilder = new StringBuilder(capacity);
for (int i = 0; i < len; i++) {
// ;{name}={value}
stringBuilder.append(separator).append(name).append(joiner).append(values[i]);
}
return stringBuilder.toString();
}
protected static String getFirst(Map> parameters, String name) {
List values = parameters.get(name);
return values == null || values.isEmpty() ? null : values.get(0);
}
private static int indexOfArchiveEntry(String path) {
return path.indexOf(ARCHIVE_ENTRY_SEPARATOR);
}
}