com.intellij.openapi.util.URLUtil Maven / Gradle / Ivy
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.util;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public final class URLUtil {
public static final String SCHEME_SEPARATOR = "://";
public static final String FILE_PROTOCOL = "file";
public static final String JAR_PROTOCOL = "jar";
public static final String JAR_SEPARATOR = "!/";
private URLUtil() {
}
/**
* Opens a url stream. The semantics is the sames as {@link URL#openStream()}. The
* separate method is needed, since jar URLs open jars via JarFactory and thus keep them
* mapped into memory.
*/
public static @NotNull InputStream openStream(@NotNull URL url) throws IOException {
String protocol = url.getProtocol();
return protocol.equals(JAR_PROTOCOL) ? openJarStream(url) : url.openStream();
}
private static @NotNull InputStream openJarStream(@NotNull URL url) throws IOException {
Pair paths = splitJarUrl(url.getFile());
if (paths == null) {
throw new MalformedURLException(url.getFile());
}
final ZipFile zipFile = new ZipFile(paths.first);
ZipEntry zipEntry = zipFile.getEntry(paths.second);
if (zipEntry == null) {
zipFile.close();
throw new FileNotFoundException("Entry " + paths.second + " not found in " + paths.first);
}
return new FilterInputStream(zipFile.getInputStream(zipEntry)) {
@Override
public void close() throws IOException {
super.close();
zipFile.close();
}
};
}
/**
* Splits .jar URL along a separator and strips "jar" and "file" prefixes if any.
* Returns a pair of path to a .jar file and entry name inside a .jar, or null if the URL does not contain a separator.
*
* E.g. "jar:file:///path/to/jar.jar!/resource.xml" is converted into ["/path/to/jar.jar", "resource.xml"].
*
* Please note that the first part is platform-dependent - see UrlUtilTest.testJarUrlSplitter() for examples.
*/
public static @Nullable Pair splitJarUrl(@NotNull String url) {
int pivot = url.indexOf(JAR_SEPARATOR);
if (pivot < 0) return null;
String resourcePath = url.substring(pivot + 2);
String jarPath = url.substring(0, pivot);
if (StringUtil.startsWithConcatenation(jarPath, JAR_PROTOCOL, ":")) {
jarPath = jarPath.substring(JAR_PROTOCOL.length() + 1);
}
if (jarPath.startsWith(FILE_PROTOCOL)) {
try {
jarPath = urlToFile(new URL(jarPath)).getPath().replace('\\', '/');
} catch (Exception e) {
jarPath = jarPath.substring(FILE_PROTOCOL.length());
if (jarPath.startsWith(SCHEME_SEPARATOR)) {
jarPath = jarPath.substring(SCHEME_SEPARATOR.length());
} else if (StringUtil.startsWithChar(jarPath, ':')) {
jarPath = jarPath.substring(1);
}
}
}
return new Pair<>(jarPath, resourcePath);
}
public static @NotNull File urlToFile(@NotNull URL url) {
try {
return new File(url.toURI().getSchemeSpecificPart());
} catch (URISyntaxException e) {
throw new IllegalArgumentException("URL='" + url + "'", e);
}
}
}