Zasób Java jako plik

122

Czy w Javie istnieje sposób na skonstruowanie instancji File na zasobie pobranym z pliku jar za pomocą modułu ładującego klasy?

Moja aplikacja używa niektórych plików z jar (domyślnie) lub z katalogu systemu plików określonego w czasie wykonywania (dane wejściowe użytkownika). Szukam spójnego sposobu
a) załadowania tych plików jako strumienia
b) wypisania plików odpowiednio w katalogu zdefiniowanym przez użytkownika lub katalogu w jar

Edycja: Najwyraźniej idealnym podejściem byłoby trzymanie się z daleka od java.io.File. Czy istnieje sposób, aby załadować katalog ze ścieżki klas i wyświetlić jego zawartość (zawarte w nim pliki / encje)?

Mantrum
źródło

Odpowiedzi:

58

ClassLoader.getResourceAsStreami Class.getResourceAsStreamsą zdecydowanie sposobem na ładowanie danych zasobów. Jednak nie wierzę, że istnieje sposób na „wylistowanie” zawartości elementu ścieżki klas.

W niektórych przypadkach może to być po prostu niemożliwe - na przykład ClassLoader może generować dane w locie na podstawie nazwy zasobu, o którą jest proszony. Jeśli spojrzysz na ClassLoaderAPI (które jest w zasadzie tym, przez co działa mechanizm ścieżki klas), zobaczysz, że nie ma nic do zrobienia tego, co chcesz.

Jeśli wiesz, że faktycznie masz plik jar, możesz go załadować, ZipInputStreamaby dowiedzieć się, co jest dostępne. Będzie to jednak oznaczać, że będziesz mieć inny kod dla katalogów i plików jar.

Jedną alternatywą, jeśli pliki są najpierw tworzone osobno, jest dołączenie pewnego rodzaju pliku manifestu zawierającego listę dostępnych zasobów. Umieść go w pliku jar lub dołącz go do systemu plików jako plik i załaduj, zanim zaoferujesz użytkownikowi wybór zasobów.

Jon Skeet
źródło
3
Zgadzam się, że to denerwujące - ale sprawia, że ​​ClassLoader ma szersze zastosowanie na inne sposoby. Na przykład, łatwo jest napisać „web classloader”, ponieważ sieć jest dobra do pobierania plików, ale zazwyczaj nie wyświetla plików.
Jon Skeet,
Wygląda na to, że jeśli zasób, który próbujesz załadować, jest znacznie większy niż 1MiB InputStream, otrzymujesz z getResourceAsStreamzatrzymań, aby pobrać bajty zasobu po tym rozmiarze, a zamiast tego zwraca 0, jeśli jest zawarty w skompresowanym systemie plików, takim jak jar. Wydaje się, getResourceże w takim przypadku jesteś zmuszony używać i ładować plik niezależnie od tego.
mgttlinger
1
@mgttlinger: Wydaje mi się to mało prawdopodobne ... prawdopodobnie warto opublikować nowe pytanie z powtórzeniem, jeśli możesz.
Jon Skeet,
Problem wynikał z readmetody decydującej o tym, ile danych odczytać (nie byłem tego świadomy). I wydaje się, że cały plik jest czytany, jeśli czytany plik znajduje się w zwykłym folderze. Jeśli plik znajduje się w słoiku, ponieważ został zapakowany, odczytuje tylko jego część.
mgttlinger
1
@mgttlinger: Nie, nie tylko czyta jego części, ale może nie wypełniać całego bufora dostarczanego przy każdym wywołaniu odczytu. Jak zawsze InputStream, jeśli chcesz przeczytać cały zasób, powinieneś zapętlić aż do readzwrócenia -1.
Jon Skeet,
98

Miałem ten sam problem i mogłem skorzystać z:

// Load the directory as a resource
URL dir_url = ClassLoader.getSystemResource(dir_path);
// Turn the resource into a File object
File dir = new File(dir_url.toURI());
// List the directory
String files = dir.list()
Chris Conway
źródło
45
To podejście nie działa z plikami JAR w ścieżce klas, tylko z plikami i katalogami
yegor256
1
@ yegor256 Jeśli twój plik jest w słoiku, możesz stworzyć FileSystemobiekt i pobrać Pathz niego. Następnie możesz to przeczytać jak zwykle. (patrz stackoverflow.com/a/22605905/1876344 )
mgttlinger
53

Oto fragment kodu z jednej z moich aplikacji ... Daj mi znać, jeśli odpowiada Twoim potrzebom. Możesz tego użyć, jeśli znasz plik, którego chcesz użyć.

URL defaultImage = ClassA.class.getResource("/packageA/subPackage/image-name.png");
File imageFile = new File(defaultImage.toURI());

Mam nadzieję, że to pomoże.

Maurice Rogers
źródło
Tak jest, ale toURI()zawiedzie.
Olivier Faucheux
10

Niezawodny sposób na skonstruowanie instancji File na zasobie pobranym z jar polega na skopiowaniu zasobu jako strumienia do pliku tymczasowego (plik tymczasowy zostanie usunięty po zamknięciu maszyny JVM):

public static File getResourceAsFile(String resourcePath) {
    try {
        InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath);
        if (in == null) {
            return null;
        }

        File tempFile = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
        tempFile.deleteOnExit();

        try (FileOutputStream out = new FileOutputStream(tempFile)) {
            //copy stream
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        }
        return tempFile;
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}
Lukas Masuch
źródło
4

Spróbuj tego:

ClassLoader.getResourceAsStream ("some/pkg/resource.properties");

Dostępnych jest więcej metod, np. Patrz tutaj: http://www.javaworld.com/javaworld/javaqa/2003-08/01-qa-0808-property.html

topchef
źródło
Dzięki za twoją odpowiedź. Wiem o getResourceAsStream, ale nie sądzę, żebym mógł załadować katalog ze ścieżki klas przez to.
Mantrum
W katalogu Java jest plik (chyba korzenie UNIX), więc powinieneś przynajmniej spróbować.
topchef
Myślę, że aby wyświetlić listę plików w katalogu, w którym będziesz musiał użyć java.io.File. Możesz wyszukiwać pliki za pomocą ClassLoader.findResource, która zwraca adres URL, który można przekazać do pliku.
Nathan Voxland,
2

To jest jedna opcja: http://www.uofr.net/~greg/java/get-resource-listing.html

Ryba
źródło
4
Chociaż ten link może odpowiedzieć na pytanie, lepiej byłoby zawrzeć tutaj zasadnicze części odpowiedzi i podać link w celach informacyjnych, ponieważ łącze może się zmienić lub strona, do której prowadzi łącze, może zostać usunięta, przez co odpowiedź będzie bezużyteczna.
JonasCz - Przywróć Monikę