Forum Clubic

[JAVA] Lister le contenu d'un paquetage

Problème du jour :stuck_out_tongue:

Comme l’indique le sujet, je souhaiterais lister toutes les classes présentes dans un paquetage, connu de la JVM (connu du ClassLoader donc). Est-ce possible ?

Par la suite, une fois ce problème réglé, serait-il possible de créer un objet à partir d’une des classes listées ?

Sur ce coup, google ne m’a pas été d’un grand secours … Si qqun a un lien :wink:

hum…
http://www.google.fr/search?q=site%3Aclubi…lla:fr:official

donc pour résumer, c’est pas simple, mais tu est obligé de relire les classes comme le fait le classLoader.
:slight_smile:

En les ouvrant avec eclipse?

Oui, effectivement, j’ai pas cherché sur Clubic.

Bon, si j’ai bien compris, je dois créer un nouveau URLClassLoader (avec un tableau d’une URL contenant une entrée : mon url vers mon paquetage ?). Ensuite, je récupère toutes les URLs que URLClassLoader veut bien me retourner. De là, j’ai ce que je veux ? (Faut que je vois à quoi ressemble le toString d’un objet URL).

Et à partir de ces entrée, je suis en mesure de charger une classe à partir d’un URL récupéré précédemment ?

EDIT : non … c’est visiblement pas ça ^^

Oui, c’est un peu plus compliqué: les url de URLClassLoader te permet simplement de retrouver le classpath paramétré pour ta JVM, mais si tu connaît le chemin d’accès de on jar, tu n’es pas obligé de passer par là.
La partie difficile, c’est d’avoir la liste des noms de classes à ouvrir si tu ne la connaît pas, c’était le but de l’autre topic.

Un fois que tu as le nom de la classe, il te suffit de récupérer l’objet “java.lang.Class” par clazz =Class.forName(classname). et cet objet te permet d’instancier de nouveaux objets simplement par clazz.newInstance().

Edit: il y a une astuce pour ne pas passer par le ClassLoader et obtenir le nom de jar d’un package, en utilisant:
http://java.sun.com/j2se/1.4.2/docs/api/ja…va.lang.String)


    URL url = this.getClass().getResource("/"+packageName.replace('.','/'));
    if (url == null){
      return "not found";
    }
    if (traceJar) LogManager.trace(packageName + " is loaded from "+url);
    if (! url.getProtocol().equals("jar")){
      return "not a jar";
    }
    int a=url.getPath().indexOf('!');
    // (ie extract the name between file:/D:/ and !
    String jarfile = url.getPath().substring(5,a); 
    //open the jar file, the manifest and then use the values we need
    Manifest man = loadManifest(jarfile);

et avec un fonction qui retourne simplement le manifest (tu n’en a pas besoin, mais ça utilise un JarFile dont tu aura besoin si tu veux parcourir des clases


  /**
   * Returns the Manifest for the specified JAR file name.
   * @see class Package
   */
  protected static Manifest loadManifest(String fn) {
    try {
        FileInputStream fis = new FileInputStream(fn);
        JarInputStream jis = new JarInputStream(fis, false);
        Manifest man = jis.getManifest();
        jis.close();
        return man;
    } catch (IOException e) {
        return null;
    }
  }

Oui, j’ai bien que c’était plus compliqué :wink: Je me casse les dents dessus là :sweet:

J’ai zappé le URLClassLoader. En fait, pour le moment, je souhaiterais récupérer le chemin vers mon paquetage (pas forcément “jarré” !!!), puis ouvrir le répertoire (dans un jar ou pas) et lister son contenu, pour récuperer l’ensemble des classes qui sont dedans.

Exemple :

public String[] getSyntaxes() {

  String syntaxPackage = "x.ldap.syntax";
  String[] result = null;

  try {

  	String path = getClass().getName() + ".class";
  	System.out.println( path );
  	URL url = getClass().getResource( path );
  	System.out.println( url );
  	path = URLDecoder.decode( url.toString(), "UTF-8" );

  	// suppression de  la classe ou du jar du path de l'url
  	int index = path.lastIndexOf( "/" );
  	path = path.substring( 0, index );

  	System.out.println( path );

  	// if( path.startsWith( "jar:file:" ) ) {
    // suppression de jar:file: de l'url d'un jar
    // ainsi que du path de la classe dans le jar

    //index = path.indexOf("!");
    //path = path.substring(9, index);
	
  	// } else {

  	if( path.startsWith( "file:/" ) ) {
    // suppresion du file: de l'url si c'est une classe en dehors d'un
    // jar et suppression du path du package si il est présent.

    path = path.substring(6, path.length());
    Package pack = getClass().getPackage();
	
    if( null != pack ) {
    	String packPath = pack.toString().replace( '.', '/' );
	
    	if( path.endsWith( packPath ) ) {
      path = path.substring( 0, ( path.length() - packPath.length() ) );
    	}
    }

    // On a récupéré le path, on attache le chemin du paquetage voulu.
    path += "/" + syntaxPackage.replace( '.', '/' );
	
    // On créé le fichier associé pour parcourir son contenu.
    // En l'occurence, c'est un dossier.
    File[] files = (new File( path )).listFiles();

    // On liste tous les fichiers qui sont dedans.
    // On les stocke dans un vecteur pour ensuite les mettres dans
    // le tableau de resultat.
    Vector<File> vectTmp = new Vector<File>();
    for( File f: files ) {
    	if( !f.isDirectory() )
      vectTmp.add( f );
    }

    result = new String[vectTmp.size()];
    for( int i=0; i<vectTmp.size(); i++ ) {
    	result[i] = vectTmp.elementAt( i ).getName();
    	i++;
    }
  	}

  } catch( Exception e ) {
  	// Erreur chargement nom de classe.
  	System.out.println( e );
  }

  return result;

	}

Mais il semble qu’il y ait une erreur dès les premières lignes : url est null.
Cette méthode est dans une classe de mon paquetage x. J’ai testé ce code dans une classe en dehors de mon paquetage et il semble que ca marche. J’ai du mal à suivre :confused:

Je ne sais pas pourquoi tu ne peut pas récupérer l’url de la resource de ton .class, mais tu peux directement obtenir la resource sur ton nom de package, comme dans mon exemple.

Ensuite, je ne comprend pas pourquoi qut fait un URL.decode, moi, je n’avais pas besoin…
Tu peux donc pas mal simplifier en prenant la resource directement sur le package, et en parcourant sur le chemin obtenu en enlevant la partie “protocole” file://

:clap:

Le code suivant marche! Merci, pour ton code, comme tu l’as dis, c’est beaucoup plus simple. il faut que je complète avec la prise en charge du jar maintenant.

	public String[] getSyntaxes() {

  String syntaxPackage = "x.ldap.syntax";
  String[] result = null;

  try {

  	URL url = this.getClass().getResource(
    	"/" + syntaxPackage.replace( '.', '/' ) );

  	if( url==null )
    return null;

  	if( url.getProtocol().equals( "jar" ) ) {
    System.out.println( "jar" );
  	
    // int a = url.getPath().indexOf('!');
    // (ie extract the name between file:/D:/ and !
    // String jarfile = url.getPath().substring(5,a);
    // open the jar file, the manifest and then use the values we need
    // Manifest man = loadManifest(jarfile);
  	
  	} else if( url.getProtocol().equals( "file" ) ) {

    String path = url.getPath();
    System.out.println( path );

    // On créé le fichier associé pour parcourir son contenu.
    // En l'occurence, c'est un dossier.
    File[] files = (new File( path )).listFiles();

    // On liste tous les fichiers qui sont dedans.
    // On les stocke dans un vecteur ...
    Vector<File> vectTmp = new Vector<File>();
    for( File f: files ) {
    	if( !f.isDirectory() )
      vectTmp.add( f );
    }

    	// ... pour ensuite les mettres dans le tableau de resultat.
    result = new String[vectTmp.size()];
    for( int i=0; i<vectTmp.size(); i++ ) {
    	result[i] = vectTmp.elementAt( i ).getName();
    }
  	}

  } catch( Exception e ) {
  	// Erreur chargement nom de classe.
  	System.out.println( "Erreur ---->" + e );
  }

  return result;

	}

Intéressant ton code :super: c’est un problème qui revient souvent, je pense que Sun aurait pu nous fournir 2-3 trucs là-dessus (j’ai été obligé de chercher ça aussi à cause des objets “Package” qui ne sont pas toujours chargés)
La partie “jar” ne devrais pas poser de problème avec un JarInputStream.:wink:

Yep.
J’ai pas encore regardé pour le jar, parce que j’ai pas de jar sous la main ^^
Par contre, à la compilation, à la ligne concernant la création dynamique (qui marche, une fois que j’ai les noms des classes), et bien j’ai un ‘unchecked cast’.

SchemaSyntax syntax = ((Class<SchemaSyntax>) Class.forName(
      	SchemaSyntax.SYNTAX_PACKAGE + "." + syntaxName )).newInstance();

Doit y avoir un moyen de pas avoir ce warning non ?

Je ne connaissait pas ce message, ça doit être du 1.5, et lié aux casts (qu’on autorise alègrement en version antérieure) peut être :


SchemaSyntax syntax = (Class<SchemaSyntax>.forName(
      SchemaSyntax.SYNTAX_PACKAGE + "." + syntaxName )).newInstance();

??

C’est du 1.5.
Quand je lis la doc de la classe Class, la méthode forname retourne un objet Class correspondant à un objet dont on ne sait pas le type. Moi, derrière, je fais un newInstance() (sans paramêtre) qui retourne l’objet correspondant (donc du bon type). Mais comme le type n’est pas connu, le castage peut-être foireux, c’est là que le compilateur nous le signale. C’est juste un warning, et non une erreur, l’application marche dans la plupart des cas.

Sauf que je suis pointilleux ^^ Et que généralement les ‘unchecked cast’ je cherche à les régler pour ne pas avoir de surprise. Et là, je sèche …

marche pas mon truc? (faire un forName directement sur le type template Class<YourType> ) comme ça il n’y a plus de cast.

eeeeehhhh non ^^

‘)’ expected à la ligne 1 et illegal start of expression à la ligne 2.

encore un essai au hasard, après, promis, j’installe le 1.5 :slight_smile:


SchemaSyntax syntax = (new Class<SchemaSyntax>())
 .forName(SchemaSyntax.SYNTAX_PACKAGE + "." + syntaxName )
 .newInstance();

??

:pt1cable:

Class() has private access in java.lang.Class
SchemaSyntax syntax = (new Class<SchemaSyntax>())

Fait gaffe, la classe SchemaSyntax est à moi, elle est pas dans la JDK 1.5 :sarcastic:

En utilisant une annotation pardi !

@SuppressWarning("unchecked")
SchemaSyntax syntax = (Class<SchemaSyntax>.forName(
     SchemaSyntax.SYNTAX_PACKAGE + "." + syntaxName )).newInstance();

ok, faut importer la classe avec import java.lang.SuppressWarnings; et ensuite c’est @SuppressWarning[b]s/b avec un ‘s’.

Une question, c’est valable pour tout le code, ou juste pour ma ligne ?
Bon, et à part dire au compilateur qu’on l’ignore, comment on résout le problème du cast ? doit y avoir une solution non ?

Pour ta ligne :slight_smile:

ceci dit, utilise Eclipse :slight_smile: il fait tout ça comme un grand

pour ton prob de cast, tu l’auras toujours à partir du moment où tu ressors un objet en prevenant d’une Class<?>.

Mais Class<Schema…>.newInstance() ne fonctionne pas?

Bah non, le compilateur ne veut pas. J’ai l’impression qu’il n’aime pas les <> avec l’appel d’une méthode. En même temps … je n’ai pas testé Class<SchemaSyntax>.newInstance(), car SchemaSyntax est une classe abstraite. Moi, je souhaite avoir un objet du type d’une classe qui hérite de SchemaSyntax (et qui sont dans ce fameux package :ange:). Tout ça à partir d’une JComboBox.

Bref, là ca marche, merci pour l’annotation, encore un truc de java 1.5 que je n’ai pas eu le temps d’approfondir. Je reviendrais sur ce topic quand j’aurais rajouté le bout de code qui gère les .jar :slight_smile: