File-Objekt liefert mir keine Referenz in einer Jar-Datei

KoMtuR

Erfahrenes Mitglied
Hallo,

Ich hab derzeit ein Problem, was mich echt verrückt macht. Ich habe eine Art Modulsystem, wo nachträglich Jar-Dateien nachgeladen werden. Da diese Jar-Dateien vielleicht auch noch Konfigurationen enthalten, wollte ich es so lösen, dass die Klasse, die beim installieren des Moduls als Einstiegspunkt gilt, eine Annotation haben kann, welche dem eigentlichen Kernprogramm dann mitteilt, dass es das Package dieser Klasse nach Konfigurationsdateien durchsuchen soll.

Dafür hab ich extra die eine Funktion hier geschrieben:
Java:
	public static File[] getFilesInPackage(Class<?> clazz, String fileEnding) {
		if(!fileEnding.startsWith("."))
			fileEnding = "."+fileEnding;
		
		ArrayList<File> packageFiles = new ArrayList<File>();
		String newPackageName = "/" + clazz.getPackage().getName().replace('.', '/');

		//Debug Ausgabe #1
		System.out.println("Path: " + clazz.getResource(newPackageName).getFile());
		//Debug Ausgabe #2
		System.out.println("File exists: "+ new File(clazz.getResource(newPackageName).getPath()).exists());
		
		File packageDir = new File(clazz.getResource(newPackageName).getFile());
		
		if(packageDir.exists() && packageDir.isDirectory()) {
			File[] files = packageDir.listFiles();
			for(File packageFile : files) {
				System.out.println(packageFile.getName());
				if(packageFile.getName().endsWith(fileEnding)) {
					packageFiles.add(packageFile);
				}
			}
		}
		
		return packageFiles.toArray(new File[]{});
Soweit sieht es ja gut aus. Aber wenn ich nun eine Jar-Datei lade, welches die bestimmten Annotationen enthält und diese Funktion aufgerufen wird, dann kommt bei der Debug Ausgabe #1
Code:
file:/C:/path/to/module/dir/bin/modules/manager_database.jar!/net/server/modules/databasemanager
und bei der Debug Ausgabe #2 ein false.

Also die Pfadangabe in der Ausgabe #1 ist völlig korrekt (der Pfad ist natürlich anders :rolleyes: ). Nur hat Java irgendwie Probleme mir eine File-Referenz auf dieses Package zurück zu liefern.
Ich hatte mal ein Beispielprogramm geschrieben, wo dies auch normal ging, aber immer der gleiche ClassLoader vorhanden war. In diesem Beispiel ist es so, dass das Modul in einem URLClassLoader hängt und diese statische Funktion in dem normalen ClassLoader hängt, der vom System beim Aufruf genommen wird, hängt.
Mit getResourceAsStream kann ich leider nichts anfangen, weil ich ja nur das Package durchsuchen will nach bestimmten Dateien (in dem Fall Konfigurationsdateien).

Ich hoffe jemand weiß eine Lösung (wenn ich ein Package in dem gleichen Jar suche, wo diese Funktion ist, dann klappt es)

Gute Nacht KoMtuR
 
Ich glaube nicht, dass das File-Objekt Referenzen innerhalb eines JAR-Files auflösen kann. Entweder du arbeitest mit getRessourceAsStream, wenn du weißt, wie die Datei heißt od. verwendest java.util.zip.ZipInputStream.

Aber vielleicht kann das File-Objekt das doch und ich lieg hier falsch.
 
Naja das PRoblem ist ja, dass ich die Namen eigentlich nicht in der Annotation schreiben wollte, weil dann könnte ich es auch gleich anders machen ;) Dem Modulprogrammierer soll es ja einfach gemacht werden.

Hatte mir gestern noch überlegt, dass ich ein wenig Mist erzählt hatte. Ich konnte mit dem Package-Zeugs was anfangen, weil es nicht in einer Jar-Datei lag. Werde mir mal das package java.util.zip anschauen. Ich werd wohl nicht drum herum kommen dies zu nutzen.
 
Du musst beim erstellen des Jarfiles, dass du einbinden willst, sicherstellen, dass da überhaupt JarEntries für Ordner gemacht werden. Sonst bekommst du zwar Einträge für Dateien in einem Ordner, aber nicht für den Ordner selbst.

Maven erzeugt diese Einträge korrekt, Eclipse jedoch erst, wenn man bei Export -> Jar explizit "Add directory entries" anhakt. Dann sollte der Classloader für getResource("foo/bar") auch eine Resource finden.

Ich hab an diesem Problem mal 2 Tage verbracht, weil ich dachte, das wäre ein Bug im Tomcat Classloader ^^.

Was du da vor hast sieht aus, wie das Classpathscanning von Spring ;).

REINHAUN!
 
Naja ich hab folgendes vor:

Es gibt erstmal den normalen Classpath, mit dem die eigentliche Anwendung gestartet wird. Dann läd diese diverse Konfigurationen, wo dem Programm dann mitgeteilt wird, wo denn der Pfad ist für die Module, die dann zu diesem Programm hinzugeladen werden. Sozusagen habe ich nun 2 ClassLoader. Einmal den Standard-ClassLoader, der beim Start der Applikation von Java benutzt wird und dann einmal einen URLClassLoader, der als Vater den Standard-ClassLoader hat, der auf die ganzen Jar-Dateien von den Modulen zeigt.

Nun werden diese Jars alle in eine Konfiguration geladen und können nach belieben des Anwenders installiert werden. Bei der Installation weiß aber mein Programm nicht, wieviel und ob überhaupt Konfigurationsdateien für das Modul vorhanden sind. Deswegen hatte ich halt eine Annotation für das Modul gemacht, wo er angeben kann, dass in dem Package, wo auch die Klasse fürs installieren liegt, Konfigurationsdateien liegen, welche das eigentliche Programm dann in das vorgesehene Verzeichnis kopieren soll (das war zumindest der Ansatz von dem Code da oben ;) ). Aber da er anscheinend über Reflection + File nicht will, hab ich nun die java.util.zip-Variante genommen.

Die Jar-Dateien erstelle ich übrigens über Ant. Mit Maven kenn ich mich noch nicht wirklich aus und da bleib ich lieber bei den Leisten, die ich kenne ;)

Hier mal der Code, der nun zum Erfolg führt:
Java:
	protected void copyConfig(GamePlugin plugin) {
		Class<?> moduleClass = plugin.getModule().getClass();
		if(moduleClass.isAnnotationPresent(CopyConfig.class)) {
			File pluginFile = new File(Game.getInstance().getConfiguration("modulePath") + plugin.getJarName());
			String packageName = moduleClass.getPackage().getName().replace('.', '/') + '/';
			try {
				ZipFile jarFile = new ZipFile(pluginFile,ZipFile.OPEN_READ);
				
				Enumeration<? extends ZipEntry> entries = jarFile.entries();
				while(entries.hasMoreElements()) {
					ZipEntry entry = entries.nextElement();
					String name = entry.getName();
					if(name.startsWith(packageName)) {
						System.out.println(name);
						name = name.substring(packageName.length());
						if((name.indexOf("/") != -1) || !name.endsWith(".xml"))
							continue;
						Utilities.copyConfig(jarFile.getInputStream(entry), name);
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
	}

PS.: Sicher hätte ich das auch mit einfachen Funktionsaufrufen und einer InputStream-Übergabe seitens des Moduls aus lösen können. Ich wollte aber nicht, dass der Modulentwickler später immer eine neue Zeile hinzufügen muss, wenn er eine Konfiguration hinzufügt. So brauch er nur die Dateien in das nötige Paket zu hauen und kann damit glücklich leben und auch sicher gehen, dass die Datei beim ersten Start des Moduls auch vorhanden ist.
 
Zurück