Aby uzyskać File
dla danego Class
, istnieją dwa kroki:
- Przekonwertować
Class
do AURL
- Przekonwertować
URL
do AFile
Ważne jest, aby zrozumieć oba kroki i nie łączyć ich.
Gdy już to File
zrobisz, możesz zadzwonićgetParentFile
aby uzyskać folder zawierający, jeśli tego potrzebujesz.
Krok 1: Class
doURL
Jak omówiono w innych odpowiedziach, istnieją dwa główne sposoby znalezienia związku URL
z Class
.
URL url = Bar.class.getProtectionDomain().getCodeSource().getLocation();
URL url = Bar.class.getResource(Bar.class.getSimpleName() + ".class");
Oba mają zalety i wady.
getProtectionDomain
Podejście daje położenie podstawy klasy (na przykład zawierający plik JAR). Możliwe jest jednak, że zasady bezpieczeństwa środowiska wykonawczego Java zostaną wyrzucone SecurityException
podczas wywoływaniagetProtectionDomain()
, więc jeśli aplikacja musi działać w różnych środowiskach, najlepiej przetestować je we wszystkich.
getResource
Podejście daje pełną ścieżkę URL zasobu klasy, z której trzeba będzie wykonać dodatkowe łańcuchach. Może to być file:
ścieżka, ale może to być jar:file:
lub nawet coś paskudniejszego bundleresource://346.fwk2106232034:4/foo/Bar.class
podczas wykonywania w środowisku OSGi. I odwrotnie, getProtectionDomain
podejście poprawnie daje afile:
adres URL nawet z poziomu OSGi.
Zauważ, że jedno getResource("")
i drugie getResource(".")
nie powiodło się w moich testach, gdy klasa rezydowała w pliku JAR; oba wywołania zwróciły wartość null. Polecam więc wywołanie nr 2 pokazane powyżej, ponieważ wydaje się bezpieczniejsze.
Krok 2: URL
doFile
Tak czy inaczej, kiedy już masz URL
, następnym krokiem jest konwersja na File
. To jest własne wyzwanie; zobacz post na blogu Kohsuke Kawaguchi, aby uzyskać szczegółowe informacje, ale w skrócie możesz użyć, new File(url.toURI())
o ile adres URL jest całkowicie dobrze sformułowany.
Na koniec odradzałbym używanie URLDecoder
. Niektórzy bohaterowie zawartości, :
a /
w szczególności nie może zawierać znaków URL-zakodowane. Z URLDecoder Javadoc:
Zakłada się, że wszystkie znaki w zakodowanym ciągu są jednym z następujących: „a” do „z”, „A” do „Z”, „0” do „9” oraz „-”, „_”, „ .", i "*". Znak „%” jest dozwolony, ale jest interpretowany jako początek specjalnej sekwencji ucieczki.
...
Istnieją dwa możliwe sposoby, w jakie ten dekoder radzi sobie z nielegalnymi łańcuchami. Może pozostawić nielegalne znaki w spokoju lub zgłosić wyjątek IllegalArgumentException. To, jakie podejście przyjmuje dekoder, należy do implementacji.
W praktyce na URLDecoder
ogół nie rzuca się IllegalArgumentException
tak, jak grozi powyżej. A jeśli ścieżka do pliku ma spacje zakodowane jako %20
, to podejście może wydawać się działać. Jeśli jednak ścieżka do pliku zawiera inne znaki niealfameryczne, takie jak +
problemy z URLDecoder
manipulowaniem ścieżką.
Działający kod
Aby wykonać te kroki, możesz mieć metody takie jak następujące:
/**
* Gets the base location of the given class.
* <p>
* If the class is directly on the file system (e.g.,
* "/path/to/my/package/MyClass.class") then it will return the base directory
* (e.g., "file:/path/to").
* </p>
* <p>
* If the class is within a JAR file (e.g.,
* "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the
* path to the JAR (e.g., "file:/path/to/my-jar.jar").
* </p>
*
* @param c The class whose location is desired.
* @see FileUtils#urlToFile(URL) to convert the result to a {@link File}.
*/
public static URL getLocation(final Class<?> c) {
if (c == null) return null; // could not load the class
// try the easy way first
try {
final URL codeSourceLocation =
c.getProtectionDomain().getCodeSource().getLocation();
if (codeSourceLocation != null) return codeSourceLocation;
}
catch (final SecurityException e) {
// NB: Cannot access protection domain.
}
catch (final NullPointerException e) {
// NB: Protection domain or code source is null.
}
// NB: The easy way failed, so we try the hard way. We ask for the class
// itself as a resource, then strip the class's path from the URL string,
// leaving the base path.
// get the class's raw resource path
final URL classResource = c.getResource(c.getSimpleName() + ".class");
if (classResource == null) return null; // cannot find class resource
final String url = classResource.toString();
final String suffix = c.getCanonicalName().replace('.', '/') + ".class";
if (!url.endsWith(suffix)) return null; // weird URL
// strip the class's path from the URL string
final String base = url.substring(0, url.length() - suffix.length());
String path = base;
// remove the "jar:" prefix and "!/" suffix, if present
if (path.startsWith("jar:")) path = path.substring(4, path.length() - 2);
try {
return new URL(path);
}
catch (final MalformedURLException e) {
e.printStackTrace();
return null;
}
}
/**
* Converts the given {@link URL} to its corresponding {@link File}.
* <p>
* This method is similar to calling {@code new File(url.toURI())} except that
* it also handles "jar:file:" URLs, returning the path to the JAR file.
* </p>
*
* @param url The URL to convert.
* @return A file path suitable for use with e.g. {@link FileInputStream}
* @throws IllegalArgumentException if the URL does not correspond to a file.
*/
public static File urlToFile(final URL url) {
return url == null ? null : urlToFile(url.toString());
}
/**
* Converts the given URL string to its corresponding {@link File}.
*
* @param url The URL to convert.
* @return A file path suitable for use with e.g. {@link FileInputStream}
* @throws IllegalArgumentException if the URL does not correspond to a file.
*/
public static File urlToFile(final String url) {
String path = url;
if (path.startsWith("jar:")) {
// remove "jar:" prefix and "!/" suffix
final int index = path.indexOf("!/");
path = path.substring(4, index);
}
try {
if (PlatformUtils.isWindows() && path.matches("file:[A-Za-z]:.*")) {
path = "file:/" + path.substring(5);
}
return new File(new URL(path).toURI());
}
catch (final MalformedURLException e) {
// NB: URL is not completely well-formed.
}
catch (final URISyntaxException e) {
// NB: URL is not completely well-formed.
}
if (path.startsWith("file:")) {
// pass through the URL as-is, minus "file:" prefix
path = path.substring(5);
return new File(path);
}
throw new IllegalArgumentException("Invalid URL: " + url);
}
Te metody można znaleźć w bibliotece SciJava Common :