package co.codewizards.cloudstore.core.util; import static co.codewizards.cloudstore.core.oio.OioFileFactory.*; import static co.codewizards.cloudstore.core.util.AssertUtil.*; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import co.codewizards.cloudstore.core.oio.File; public final class UrlUtil { private static final Logger logger = LoggerFactory.getLogger(UrlUtil.class); public static final String PROTOCOL_FILE = "file"; public static final String PROTOCOL_JAR = "jar"; private UrlUtil() { } public static URL canonicalizeURL(final URL url) { if (url == null) return null; URL result = url; String query = url.getQuery(); if (query != null && query.isEmpty()) { query = null; result = null; } String path = url.getPath(); while (path.endsWith("/")) { path = path.substring(0, path.length() - 1); result = null; } if (result == null) { final String file = query == null ? path : path + '?' + query; try { result = new URL(url.getProtocol(), url.getHost(), url.getPort(), file); } catch (final MalformedURLException e) { throw new RuntimeException(e); } } return result; } public static File getFile(final URL url) { assertNotNull(url, "url"); if (!url.getProtocol().equalsIgnoreCase(PROTOCOL_FILE)) throw new IllegalStateException("url does not reference a local file, i.e. it does not start with 'file:': " + url); try { return createFile(url.toURI()); } catch (final URISyntaxException e) { throw new RuntimeException(e); } } /** * Appends the URL-encoded {@code path} to the given base {@code url}. * <p> * This method does <i>not</i> use {@link java.net.URLEncoder URLEncoder}, because of * <a href="https://java.net/jira/browse/JERSEY-417">JERSEY-417</a>. * @param url the URL to be appended. Must not be <code>null</code>. * @param path the path to append. May be <code>null</code>. It is assumed that this * path is already encoded. It is therefore <b>not</b> modified at all and appended * as-is. * @return the URL composed of the prefix {@code url} and the suffix {@code path}. * @see #appendNonEncodedPath(URL, String) */ public static URL appendEncodedPath(final URL url, final String path) { assertNotNull(url, "url"); if (path == null || path.isEmpty()) return url; return appendEncodedPath(url, Collections.singletonList(path)); } /** * Appends the plain {@code path} to the given base {@code url}. * <p> * Each path segment (the text between '/') is separately {@linkplain UrlEncoder URL-encoded}. A * '/' itself is therefore conserved and not encoded. * @param url the URL to be appended. Must not be <code>null</code>. * @param path the path to append. May be <code>null</code>. * @return the URL composed of the prefix {@code url} and the suffix {@code path}. * @see #appendEncodedPath(URL, String) */ public static URL appendNonEncodedPath(final URL url, final String path) { assertNotNull(url, "url"); if (path == null || path.isEmpty()) return url; final String[] pathSegments = path.split("/"); final List<String> encodedPathSegments = new ArrayList<String>(pathSegments.length); for (final String pathSegment : pathSegments) { encodedPathSegments.add(UrlEncoder.encode(pathSegment)); } return appendEncodedPath(url, encodedPathSegments); } private static URL appendEncodedPath(final URL url, final List<String> pathSegments) { assertNotNull(url, "url"); if (pathSegments == null || pathSegments.isEmpty()) return url; try { final StringBuilder urlString = new StringBuilder(url.toExternalForm()); for (final String ps : pathSegments) { if (ps == null || ps.isEmpty()) continue; if (ps.startsWith("/") && getLastChar(urlString) == '/') urlString.append(ps.substring(1)); else if (!ps.startsWith("/") && getLastChar(urlString) != '/') urlString.append('/').append(ps); else urlString.append(ps); } return new URL(urlString.toString()); } catch (final MalformedURLException e) { throw new IllegalArgumentException(e); } } private static char getLastChar(final StringBuilder stringBuilder) { assertNotNull(stringBuilder, "stringBuilder"); final int index = stringBuilder.length() - 1; if (index < 0) return 0; return stringBuilder.charAt(index); } /** * Convert an URL to an URI. * @param url The URL to cenvert * @return The URI */ public static final URI urlToUri(final URL url) { if (url == null) return null; try { return new URI(url.getProtocol(), url.getAuthority(), url.getPath(), url.getQuery(), url.getRef()); } catch (final URISyntaxException e) { // Since every URL is an URI, its transformation should never fail. But if it does, we rethrow. throw new RuntimeException(e); } } /** * Gets the File referencing the JAR. * * @param url the url to be unwrapped. Must not be <code>null</code>. Must be a JAR-URL (i.e. protocol must be {@link #PROTOCOL_JAR})! * @return the unwrapped URL, i.e. usually the 'file:'-URL pointing to the JAR-URL. */ public static File getFileFromJarUrl(final URL url) { URL fileUrl = getFileUrlFromJarUrl(url); return getFile(fileUrl); } /** * Removes the 'jar:'-prefix and the '!...'-suffix in order to unwrap the 'file:'-URL pointing to the JAR. * * @param url the url to be unwrapped. Must not be <code>null</code>. Must be a JAR-URL (i.e. protocol must be {@link #PROTOCOL_JAR})! * @return the unwrapped URL, i.e. usually the 'file:'-URL pointing to the JAR-URL. */ public static URL getFileUrlFromJarUrl(final URL url) { // TODO nested JARs not yet supported! assertNotNull(url, "url"); logger.debug("getFileUrlFromJarUrl: url={}", url); if (!url.getProtocol().equalsIgnoreCase(PROTOCOL_JAR)) throw new IllegalArgumentException("url is not starting with 'jar:': " + url); String urlStrWithoutJarPrefix = url.getFile(); final int exclamationMarkIndex = urlStrWithoutJarPrefix.indexOf('!'); if (exclamationMarkIndex >= 0) { urlStrWithoutJarPrefix = urlStrWithoutJarPrefix.substring(0, exclamationMarkIndex); } try { final URL urlWithoutJarPrefixAndSuffix = new URL(urlStrWithoutJarPrefix); logger.debug("getFileUrlFromJarUrl: urlWithoutJarPrefixAndSuffix={}", urlWithoutJarPrefixAndSuffix); return urlWithoutJarPrefixAndSuffix; } catch (final MalformedURLException e) { throw new RuntimeException(e); } } }