Para obter File
um dado Class
, há duas etapas:
- Converta
Class
emURL
- Converta
URL
emFile
É importante entender os dois passos, e não confundi-los.
Depois de ter o File
, você pode ligar getParentFile
para obter a pasta que contém, se é isso que precisa.
Etapa 1: Class
paraURL
Conforme discutido em outras respostas, há duas maneiras principais de encontrar um URL
relevante para a Class
.
URL url = Bar.class.getProtectionDomain().getCodeSource().getLocation();
URL url = Bar.class.getResource(Bar.class.getSimpleName() + ".class");
Ambos têm prós e contras.
A getProtectionDomain
abordagem gera a localização base da classe (por exemplo, o arquivo JAR que contém). No entanto, é possível que a política de segurança do tempo de execução Java seja lançada SecurityException
ao chamar getProtectionDomain()
, portanto, se seu aplicativo precisar executar em uma variedade de ambientes, é melhor testar em todos eles.
A getResource
abordagem gera o caminho completo do recurso de URL da classe, a partir do qual você precisará executar uma manipulação adicional de strings. Pode ser um file:
caminho, mas também pode ser jar:file:
ou até algo mais desagradável bundleresource://346.fwk2106232034:4/foo/Bar.class
ao executar dentro de uma estrutura OSGi. Por outro lado, a getProtectionDomain
abordagem gera corretamente uma file:
URL mesmo dentro do OSGi.
Observe que ambos getResource("")
e getResource(".")
falharam nos meus testes, quando a classe residia em um arquivo JAR; ambas as invocações retornaram nulas. Por isso, recomendo a invocação nº 2 mostrada acima, pois parece mais segura.
Etapa 2: URL
paraFile
De qualquer forma, depois de ter um URL
, o próximo passo é converter para a File
. Este é o seu próprio desafio; consulte a publicação no blog de Kohsuke Kawaguchi para obter detalhes completos, mas, em suma, você pode usar new File(url.toURI())
desde que a URL esteja completamente bem formada.
Por fim, eu desencorajaria o uso URLDecoder
. Alguns caracteres da URL :
e , /
em particular, não são caracteres codificados por URL válidos. No URLDecoder Javadoc:
Supõe-se que todos os caracteres na cadeia codificada sejam um dos seguintes: "a" a "z", "A" a "Z", "0" a "9" e "-", "_", " . "e" * ". O caractere "%" é permitido, mas é interpretado como o início de uma sequência de escape especial.
...
Existem duas maneiras possíveis pelas quais esse decodificador pode lidar com cadeias ilegais. Ele pode deixar caracteres ilegais em paz ou lançar uma IllegalArgumentException. A abordagem adotada pelo decodificador é deixada para a implementação.
Na prática, URLDecoder
geralmente não joga IllegalArgumentException
como ameaçado acima. E se o caminho do arquivo tiver espaços codificados como %20
, essa abordagem pode parecer funcionar. No entanto, se o caminho do arquivo tiver outros caracteres não alfanuméricos, como +
você terá problemas ao URLDecoder
manipular o caminho do arquivo.
Código de trabalho
Para realizar essas etapas, você pode ter métodos como o seguinte:
/**
* 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);
}
Você pode encontrar esses métodos na biblioteca comum do SciJava :