# Ressourcen dynamisch zur Laufzeit laden



## hdi (7. Jan 2011)

Hey,

die Frage wäre: Kann ich die Dateien in einem package _zur Laufzeit_ ermitteln? Also nicht Dateien in einem externen Jar, sondern in einem Package innerhalb meines Projekts?

Hintergrund: Ich habe ein /icons package, das all meine GUI-Icons enthält. Im Moment lade ich die alle einzeln per


```
ImageIO.read(MyIcons.class.getResource("/icons/test.png"));
```

Natürlich hab ich mir das mit ner VarArg-Methode und ner Schleife schon so angenehm wie möglich gemacht, trotzdem muss ich den Methodenaufruf jedesmal anpassen, wann immer ich ein Bild lösche/hinzufüge/umbenenne.

Das ist grad ziemlich nervig weil ich recht viele Icons hab und ich spiel grad etwas mit der Optik rum und kuck mir die verschiedensten Icons an. Schön wäre es wenn ich einfach nur das package an sich mit neuen Icons füttere und mein Programm beim Start einfach alle lädt und mir in ne Map legt (<Dateiname>, <geladenes Icon>)

Möglich? Danke!


----------



## Antoras (8. Jan 2011)

Was hindert dich daran das Jar-Archiv einfach zu durchsuchen und die benötigten Dateien zu laden? Ich hab da mal was zusammen gebastelt, hab aber keine Ahnung ob man das nicht auch ein bisschen einfacher erreichen kann:

```
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.imageio.ImageIO;


public class Main {

	public static void main(final String... args) throws IOException {
		final Map<String, BufferedImage> map = new HashMap<String, BufferedImage>();
		
		for (final JarEntry e : Collections.list(new JarFile(jarName()).entries())) {
			if (isIcon(e.getName())) {
				final String fileName = "/" + e.getName();
				map.put(fileName, ImageIO.read(Main.class.getResource(fileName)));
			}
		}
		
		for (final Map.Entry<String, BufferedImage> me : map.entrySet()) {
			System.out.println(me.getKey() + ", <a file>");
		}
	}
	
	static boolean isIcon(final String str) {
		final String[] s = str.split("/");
		return str.contains("icons/") && s[s.length - 1].endsWith(".png");
	}
	
	static String jarName() {
		final String s[] = Main.class.getProtectionDomain().getCodeSource().getLocation().toString().split("/");
		return s[s.length - 1];
	}
}
```
Das hat mir alle png-Dateien im Verzeichnis "icons" geladen. Das Problem bleibt halt, dass du vor der Kompilierung wissen musst welches Icon du welcher Komponente zuordnest. Am Komfortabelsten wäre es wohl wenn die Bild-Dateien einen Identifier der Komponente im Namen beinhalten würden auf die sie platziert werden sollen, dann müsstest du diesen nur extrahieren und die Datei der Komponente zuordnen.


----------



## hdi (8. Jan 2011)

Danke schonmal, das sieht ja genau nach dem aus was ich suche. Aber es gibt leider ein Problem: Beim Konstruktor-Aufruf von von JarFile fliegt folgende Exception:


```
java.io.FileNotFoundException: bin (Access is denied)
	at java.util.zip.ZipFile.open(Native Method)
	at java.util.zip.ZipFile.<init>(Unknown Source)
	at java.util.jar.JarFile.<init>(Unknown Source)
	at java.util.jar.JarFile.<init>(Unknown Source)
	at gui.IconPool.loadIcons(IconPool.java:32)
	at runtime.Main$1.run(Main.java:29)
	at java.awt.event.InvocationEvent.dispatch(Unknown Source)
	at java.awt.EventQueue.dispatchEvent(Unknown Source)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
	at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
	at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.awt.EventDispatchThread.run(Unknown Source)
```

Ich hab's auch schon statt dem Ergebnis von jarName() = bin, mit dem vollen Pfad versucht der von 
	
	
	
	





```
MyClass.class.getProtectionDomain().getCodeSource().getLocation().toString()
```
 zurückgegeben wird. Aber auch hier fliegt eine FileNotFoundException, mit der Bemerkung



> (The filename, directory name, or volume label syntax is incorrect)


----------



## Antoras (8. Jan 2011)

Was für einen Pfad gibt denn 
	
	
	
	





```
MyClass.class.getProtectionDomain().getCodeSource().getLocation().toString()
```
 zurück und unter welchem Pfad ist das Jar-Archive gespeichert?

Hab den obigen Code angepasst, jetzt sieht er ein bisschen übersichtlicher aus.


----------



## hdi (8. Jan 2011)

> unter welchem Pfad ist das Jar-Archive gespeichert?


Ach ich glaube wir reden aneinander vorbei. Ich habe kein Jar-Archiv. Ich dachte er kann das zur Laufzeit aus dem eigenen Projekt ziehen. Wenn ich alle Bilder in ein jar legen müsste wäre der Umstand ja wieder so groß. Jedesmal ein neues Jar erstellen wenn ich was ändere.

Nein, ich möchte dass er die Dateien innerhalb eines *packages in meinem Projekt* lädt, nicht aus irgendnem jar...


----------



## Antoras (8. Jan 2011)

Wenn du kein Jar-Archiv hast, dann kannst du ganz einfach über die Klasse 
	
	
	
	





```
File
```
 auf die Dateien zugreifen, z.B. 
	
	
	
	





```
new File("bin/icons").listFiles()
```
. Der Rest ist dann praktisch identisch zu obigen Code, nur dass die Schleife eben über die Files iteriert und nicht über die JarFiles.


----------



## hdi (8. Jan 2011)

Oh, daran hab ich gar nicht gedacht. Aber ich steh grad etwas auf dem Schlauch und - ich geb's zu - habe gerade keinen Nerv (weil dafür eig. keine Zeit) dafür.. Was für einen Pfad muss ich denn jetzt genau nehmen, damit das später als JAR auch noch funzt?

D.h. die Frage ist: Wie bekomme ich den Pfad dieses Ordners innerhalb des JARs zur Ausführungszeit?

Danke!


----------



## XHelp (8. Jan 2011)

Zuerst JAR, dann doch kein JAR, dann doch plötzlich JAR... du solltest dich mal entscheiden was du willst...
Innerhalb einer Jar kannst du nicht mit File-Objekten arbeiten.


----------



## hdi (8. Jan 2011)

> Zuerst JAR, dann doch kein JAR, dann doch plötzlich JAR... du solltest dich mal entscheiden was du willst...


Naja, ich habe kein Archiv für meine Bilder, die liegen nur in einem Package innerhalb meines Projekts. Deshalb sagte ich kein Jar. Aber natürlich soll das ganze nicht nur während der Entwicklung funktionieren, wenn ich es über Eclipse compile und starte. Sondern auch später wenn das Programm - als JAR! - ausgeliefert wird. 
Ich weiß dass es da halt einen Unterschied macht bei Pfadangaben, ob man nun einen Pfad auf der Platte angibt oder einen relativen Pfad innerhalb des Projektes, darauf zielte meine Frage ab.


----------



## XHelp (8. Jan 2011)

Ich verstehe generell nicht, was es dir bringen soll. Es gibt z.B. ein Bild 
	
	
	
	





```
add.png
```
. Dieses Bild lädst du irgendwo und verwendest es irgendwo. Dafür musst du wissen, dass das Bild 
	
	
	
	





```
add.png
```
 heißt. Selbst wenn du das Bild ersetzt, ist es doch dem Programm egal was letztendlich geladen wird.


----------



## hdi (8. Jan 2011)

Okay also: Ich hab mir für den leichteren Zugriff auf meine Icons eine Klasse "IconPool" gemacht, die in einer Map alle Bilder enthält - und die werden alle beim Programmstart geladen. Diese Bilder hol ich mir an den jeweiligen Stellen im Programm per 
	
	
	
	





```
getIcon(String)
```
 raus.

Dass ich diesen Methodenaufruf dann natürlich an diesen Stellen ändern muss, wenn ich Bilder umbennene/hinzufüge/lösche ist klar, darum geht's auch nicht. Es geht darum dass ich _zusätzlich _immer in der IconPool-Klasse rumpfuschen muss, weil ich dort eben im Moment alle Bilder einzeln lade, und da auch die Dateinamen angeben muss, siehe Aufruf von "loadIcons()":

```
public class IconPool {

	private static final Map<String, ImageIcon> icons = new HashMap<String, ImageIcon>();

	public static void populate() throws IOException {
		loadIcons("bla.png", "blub.png"); // und noch hunderte andere...
	}

	private static void loadIcons(String... names) throws IOException {
		for (String name : names) {
			icons.put(name, new ImageIcon(ImageIO.read(IconPool.class.getResource("/icons" + name))));
		}
	}

}
```

Was ich will ist dass ich da einfach eine Methode habe die mir alle Bilder aus dem package automatisch ausliest, ohne dass ich alle Bildernamen einzeln angeben muss. So müsste ich dann wirklich nur noch an den Stellen im Programm etwas ändern, wo ich mir ein Bild aus der Map ziehe, aber an der IconPool Klasse selber nichts mehr.

Die Bilder liegen nun halt NICHT in nem Jar, weil das wäre ja bei all den Änderungen auch sehr umständlich immer ein neues JAR zu generieren, da kann ich gleich die Namen im Methodenaufruf oben anpassen. Aber: Ich kann jetzt nicht mit absoluten Pfadangaben bei Files daherkommen, da er die bilder dann im Moment bei mir finden würde, aber wenn ich das Projekt als JAR exportier und dir als User schicke, dann findet er sie nicht mehr, weil sie bei dir nicht auf der Platte liegen sondern innerhalb des JARs.

Ist jetzt etwas klarer worum es mir geht? Er soll einfach alle Icons aus dem package laden, ist mir wurscht wie die heißen, beim Zugriff auf die Map weiss ich das dann schon, ich seh sie ja in meinem Package, aber ich will die nicht immer hier in dieser Klasse angeben müssen beim Namen...


----------



## Gastredner (8. Jan 2011)

Mach es doch folgendermaßen:
Zunächst lädt IconPool mit dem Code von Antoras alle Icons aus dem Jar-internen Ordner. Anschließend wird überprüft, ob es im Arbeitsverzeichnis einen Ordner "icons" gibt. Sollte dem so sein, so lädst du alle in diesem abgelegten Bilddateien und steckst sie in dieselbe Map wie die zuvor aus dem Jar geladenen. So hast du ein jederzeit verfügbares Icon-Set innerhalb deines Jars und kannst über den Ordner diese schnell austauschen.
Innerhalb deines Eclipse-Projekts deaktivierst du das Laden der Icons aus dem Jar (da dies nicht funktioniert) und lädst die Icons stattdessen ausschließlich aus einem Ordner icons, welcher direkt in deinem Projektverzeichnis liegt (einfach eine Kopie des Ordners innerhalb deiner Packagestruktur).


----------



## Antoras (8. Jan 2011)

Gastredner hat gesagt.:


> Innerhalb deines Eclipse-Projekts deaktivierst du das Laden der Icons aus dem Jar (da dies nicht funktioniert) und lädst die Icons stattdessen ausschließlich aus einem Ordner icons, welcher direkt in deinem Projektverzeichnis liegt (einfach eine Kopie des Ordners innerhalb deiner Packagestruktur).


Alternativ kannst du auch einfach eine Abfrage einbauen, die prüft ob der Code von der IDE oder von einem Jar-Archiv ausgeführt wird. Dazu würde ich einfach den Ausführungspfad der Main-Klasse ermitteln und dann prüfen ob er auf 
	
	
	
	





```
.jar
```
 endet. Das hätte den Vorteil, dass du nicht die ganze Zeit am Code rumwerkeln musst.


----------



## hdi (8. Jan 2011)

Also ich bin mir nicht sicher ob ich das richtig verstanden habe. Ihr meint:


```
try{
     // Antoras Code, d.h. mit Jar - wird später bei der Auslieferung funktionieren
}
catch(FileNotFoundException e){
     // denn die fliegt ja bei mir im Moment. Nur weil ich das Programm im Moment eben nicht über ne JAR starte?
     // Hier jetzt das ganze Spiel nur mit File statt Jar?
}
```

Ist das so? Wenn nicht hab ich die letzten 2 Beiträge leider nicht ganz verstanden.

Ich weiß außerdem nicht welchen konkreten Pfad ich verwenden soll im dem Fall dass ich nicht mir der JAR sondern mit File arbeite. Ich kenn mich leider mit diesen ganzen class-bezogenen Dingen aus der API nicht so aus. Also den Pfad zur Jar hat ja Antora gepostet, aber wie sieht der für File aus.

new File(???)

Meine Struktur:
IconPool liegt in package gui
Bilder liegen in package gui.icons

Danke!


----------



## Gastredner (8. Jan 2011)

[c]new File("./icons");[/c] für den Ordner icons, würde ich mal sagen. Der Punkt sollte sogar unnötig sein, da File-Objekte bei fehlender Laufwerksangabe bzw. ohne Filesystem-Root mit relativen Pfaden arbeiten (relativ zum aktuellen Arbeitsverzeichnis). Ansonsten gäbe es noch die sichere Variante [c]new File(System.getProperty("user.dir") + "/icons");[/c]

Und nein, so hatte zumindest ich mir das nicht vorgestellt. Du könntest z. B. zwei Methoden schreiben: [c]loadFromArchive()[/c] und [c]loadFromDirectory()[/c]. Erstere würde versuchen, die Icons mit der von Antoras aufgezeigten Methode aus dem Jar zu laden. Entweder kommentierst du den Aufruf dieser Methode aus, wenn du in Eclipse arbeitest, oder du schreibst sie so, dass sie einfach abbricht, sollte dein Code nicht in einem .jar liegen. Die zweite Methode sucht nach dem externen Ordner und lädt, sofern er existiert, die dortigen Icons, die dann u. U. die zuvor geladenen auch überschreiben.
Wenn du im Eclipse-Projekt dann noch eine Kopie deines icon-packages direkt im Projektordner selbst erstellst (damit [c]loadFromDirectory[/c] diese auslesen kann), dann sollte es kein Problem mehr sein, Icons schnell mal auszutauschen.


----------



## Runtime (9. Jan 2011)

Eine Alternative wäre eine Datei, in der alle Dateinamen gespeichert sind. Die Dateinamen kannst du von diesem Programm in die Datei schreiben lassen. (Ungetestet!)

```
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;

public class Main {

    public static void main(String[] args) throws FileNotFoundException {
        File dir = new File(args[0]);
        File[] files = dir.listFiles();
        File file = new File(dir, "output.txt");
        PrintStream out = new PrintStream(file);
        for(File f : files) {
            if(f.isFile()) {
                out.println(f.getName());
            }
        }
        out.close();
    }

}
```


----------

