Obter lista de classes da aplicação [RESOLVIDO]

Olá pessoal, cá estou com outra dúvida complicada…

é o seguinte:

Fiz um framework baseado em anotações, a idéia é o programador apenas utilizar o jar do framework, anotar as classes dele com minhas anotações e tudo funcionar como mágica.

Rodando pelo eclipse, funcionava muito bem nos casos de teste. Mas quando eu criava o pacote jar, ocorria erros diversos de null pointer.

Estudando mais o processo, eu vi que a implementação estava funcionando porque obtinha os .class da estrutura de diretórios da pasta bin, mas quando empacoto, a estrutura fica interna no jar. Blz, é só eu ler o conteúdo do jar e tudo volta a funcionar.

Só que tem um problema a mais: as classes anotadas da aplicação não ficarão no meu jar, ou seja: não conseguirei encontrá-las sem mapear as classes em algum arquivo de configuração(o q eu acho bem ruim).

Não conheço classloader, mas pelo q eu vi, ele só armazena as classes carregadas naquele momento, então não há garantias que todas as classes da aplicação estejam listadas.

Há alguma forma de obter todas as classes da aplicação? (tudo o q o sandbox daquela aplicação puder ver, eu tb gostaria de ver).

Alguém tem alguma idéia sobre isso?

[quote=barenko]O
Há alguma forma de obter todas as classes da aplicação? (tudo o q o sandbox daquela aplicação puder ver, eu tb gostaria de ver).
[/quote]

Não existe uma forma direta, mas existe forma sim.
A forma mais simples, usada por todos os frameworks que usam esse tipo de feature é deixar o programador definir o(s) pacote(s) onde estão as classes que devem ser analizadas. Dê uma olhada no método getPackageClasses doMiddleHeaven

Em tese vc pode iterar nos pacotes e sub pacotes mas isso é complexo a menos que vc tenha um sistema de arquivos virtuais incluso na sua API

Segue um exemplo de como você pode fazer:

/*
 * Brutos Web MVC http://brutos.sourceforge.net/
 * Copyright (C) 2009 Afonso Brand�o. (afonso.rbn@gmail.com)
 *
 * This library is free software. You can redistribute it 
 * and/or modify it under the terms of the GNU General Public
 * License (GPL) version 3.0 or (at your option) any later 
 * version.
 * You may obtain a copy of the License at
 * 
 * http://www.gnu.org/licenses/gpl.html 
 * 
 * Distributed WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
 * either express or implied.
 *
 */

package org.brandao.brutos;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.brandao.brutos.mapping.MappingException;
/**
 *
 * @author Afonso Brand�o
 */
public class SearchClass<T> {

    private List<Class<T>> clazzs;
    private CheckSearch check;
    
    public SearchClass(){
        this.clazzs = new ArrayList<Class<T>>();
    }
    
    public void load( ClassLoader classLoader ){
        try{
            URLClassLoader urls = (URLClassLoader)classLoader;
            for( URL url: urls.getURLs() )
                readClassPath( url, classLoader );
        }
        catch( Exception e ){}
    }

    public void loadDirs( ClassLoader classLoader ){
        try{
            URLClassLoader urls = (URLClassLoader)classLoader;
            for( URL url: urls.getURLs() )
                readClassDir( url, classLoader );
        }
        catch( Exception e ){}
    }
    
    public void manifest(){
        try {
            ClassLoader classLoader = ClassLoader.getSystemClassLoader();
            Enumeration<URL> urls = classLoader.getResources( "META-INF/MANIFEST.MF" );
            
            while( urls.hasMoreElements() ){
                URL url = urls.nextElement();
                InputStream in = url.openConnection().getInputStream();
                manifest(in, classLoader);
            }
        } catch (Exception ex) {
            throw new MappingException( ex );
        }
    }
    
    public void manifest(InputStream in, ClassLoader classLoader){
        try{
            java.io.BufferedReader reader = new java.io.BufferedReader( new java.io.InputStreamReader( in ) );
            String txt = "";
            String line = "";

            while( (line = reader.readLine() ) != null ){
                if( line.startsWith( "Class-Path: " ) ){
                    txt = line.substring( "Class-Path: ".length(), line.length() );
                    while( (line = reader.readLine() ) != null && line.startsWith( " " ) ){
                        txt += line.substring( 1, line.length() );
                    }
                }
            }
            
            StringTokenizer stok = new StringTokenizer( txt, " ", false );
            while( stok.hasMoreTokens() ){
                String dirName  = System.getProperty( "user.dir" );
                String fileName = stok.nextToken();
                fileName = dirName + "/" + fileName;
                File file = new File( fileName );
                if( file.exists() )
                    readJar( file, classLoader );
                
            }
        }
        catch( Throwable e ){
            throw new MappingException( e );
        }
     
    }
    
    private void readClassDir( URL url, ClassLoader classLoader ) throws UnsupportedEncodingException, IOException, ClassNotFoundException{
        //logger.debug( "URL: " + url.toString() );
        String path = URLDecoder.decode( url.getFile(),  "UTF-8" );
        File file = new File( path );
        if( file.isDirectory() ){
            path = file.getPath();
            readDir( file, classLoader, path.length() );
        }
    }
    
    private void readClassPath( URL url, ClassLoader classLoader ) throws UnsupportedEncodingException, IOException, ClassNotFoundException{
        //logger.debug( "URL: " + url.toString() );
        String path = URLDecoder.decode( url.getFile(),  "UTF-8" );
        File file = new File( path );
        if( file.isFile() )
            readJar( file, classLoader );
        else
        if( file.isDirectory() ){
            path = file.getPath();
            readDir( file, classLoader, path.length() );
        }
    }

    private void readDir( File dir, ClassLoader classLoader, int removePos ){
        File[] files = dir.listFiles();
        
        for( File file: files ){
            if( file.isDirectory() )
                readDir( file, classLoader, removePos );
            else
            if( file.isFile() ){
                String name = file.getPath();
                if( name.endsWith( ".class" ) ){
                    name = name.substring( removePos + 1, name.length()-6 ).replace( "/" , "." ).replace( "\\", "." );
                    try{
                        checkClass( Class.forName( name, false, classLoader) );
                    }
                    catch( Throwable e ){}
                }
            }
        }
    }
    
    private void readJar( File file, ClassLoader classLoader ) throws IOException, ClassNotFoundException{
        JarFile jar = null;
        jar = new JarFile( file );
        try{
            Enumeration<JarEntry> entrys = jar.entries();
            while( entrys.hasMoreElements() ){
                JarEntry entry = entrys.nextElement();
                String name = entry.getName();

                if( name.endsWith( ".class" ) ){
                    String tmp = name.replace( "/" , "." ).substring( 0, name.length()-6 );
                    try{
                        checkClass( Class.forName( tmp, false, classLoader) );
                    }
                    catch( Throwable e ){}
                }
                //System.out.println( entry.getName() );
            }
        }
        catch( Exception e ){
            if( jar != null )
                jar.close();
        }
        jar.close();
    }

    public void setCheck( CheckSearch check ){
        this.check = check;
    }
    
    private void checkClass( Class<?> classe ){
        if( check == null )
            throw new NullPointerException();
        
        if( check.checkClass( classe ) ){
            clazzs.add( (Class<T>)classe );
        }
    }

    public List<Class<T>> getClazzs() {
        return clazzs;
    }

    public CheckSearch getCheck() {
        return check;
    }
}
/*
 * Brutos Web MVC http://brutos.sourceforge.net/
 * Copyright (C) 2009 Afonso Brand&#65533;o. (afonso.rbn@gmail.com)
 *
 * This library is free software. You can redistribute it 
 * and/or modify it under the terms of the GNU General Public
 * License (GPL) version 3.0 or (at your option) any later 
 * version.
 * You may obtain a copy of the License at
 * 
 * http://www.gnu.org/licenses/gpl.html 
 * 
 * Distributed WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
 * either express or implied.
 *
 */

package org.brandao.brutos;

/**
 *
 * @author Afonso Brand&#65533;o
 */
public interface CheckSearch {
    
    public boolean checkClass( Class<?> classe );
    
}

Como usar:

private void loadClass(){
        sf  = new SearchClass<Class>();
        
        sf.setCheck( new CheckSearch() {

                public boolean checkClass(Class<?> classe) {
                    
                    if( classe.isAnnotationPresent( Frame.class ) || 
                        classe.isAnnotationPresent( Injectable.class ) ||
                        classe.isAnnotationPresent( Intercepts.class ) )
                        return true;
                    else
                        return false;
                       
                }
            } 
        
        );
        
        sf.load( Thread.currentThread().getContextClassLoader() );
        sf.loadDirs( Thread.currentThread().getContextClassLoader() );
}

Para obter as classes use:

List<Class> clazzs = sf.getClazzs();

plic_ploc, bacana o exemplo, é parecido com o meu… só q tb cai na mesma limitação :(. Se vc empacotar ele num jar, ele já não acha as referencias das classes… Ele funciona bem em aplicações web já q as classes ficam “explodidas”, mas falha em aplicações desktop.

sergiotaborda, tb olhei esse exemplo q vc me passou e fiz um projeto de teste. Notei que ele não acha nada que ainda não tenha sido utilizado. Você pode me dar algum exemplo de um framework que faça isso (que não seja voltado a web)? Como os pacotes são mapeados?

para isso você deve usar o método:

SearchClass.manifest();

esse método acessa o arquivo META-INF/MANIFEST.MF de todos os jars.

exemplo de um arquivo MANIFEST.MF:

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.6.5
Created-By: 10.0-b23 (Sun Microsystems Inc.)
Main-Class: Main
Class-Path: lib/log4j-gump-07042008.jar lib/substance.jar lib/Server.j
 ar lib/NewJCPTBR.jar lib/Utilitarios.jar lib/AutoEscolaPersistenceCla
 ss.jar lib/ImpressaoVisual.jar lib/proxy.jar lib/ServerStub.jar
X-COMMENT: Main-Class will be added automatically by build

Usei o método e estava dando erro de acesso negado

C:\javaProjects\RelationalDaoSplited\dist&gt;java -jar RelationalDaoSplited2.jar Exception in thread &quot;main&quot; java.io.FileNotFoundException: C:\javaProjects\RelationalDaoSplited\dist\. (Acesso negado) at java.util.zip.ZipFile.open(Native Method) at java.util.zip.ZipFile.&lt;init&gt;(Unknown Source) at java.util.jar.JarFile.&lt;init&gt;(Unknown Source) at java.util.jar.JarFile.&lt;init&gt;(Unknown Source) at main.SearchClass.readJar(SearchClass.java:123) at main.SearchClass.manifest(SearchClass.java:77) at main.SearchClass.manifest(SearchClass.java:51) at main.TCheckSearch.loadClass(TCheckSearch.java:18) at main.TCheckSearch.main(TCheckSearch.java:25)

Ai, adicionei um if no metodo manifest e funcionou:

[code] public void manifest(InputStream in, ClassLoader classLoader) throws IOException, ClassNotFoundException {

java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(in));
String txt = "";
String line = "";

while ((line = reader.readLine()) != null) {
    if (line.startsWith("Class-Path: ")) {
	txt = line.substring("Class-Path: ".length(), line.length());
	while ((line = reader.readLine()) != null && line.startsWith(" ")) {
	    txt += line.substring(1, line.length());
	}
    }
}

StringTokenizer stok = new StringTokenizer(txt, " ", false);
while (stok.hasMoreTokens()) {
    String dirName = System.getProperty("user.dir").replaceAll("\\\\", "/");
    String fileName = stok.nextToken();

//barenko- adicionei isso aqui
if (".".equals(fileName)) {
fileName = (“RelationalDaoSplited2.jar”);
}
//barenko- fim

    fileName = dirName + "/" + fileName;
    File file = new File(fileName);
    if (file.exists()) readJar(file, classLoader);
}

}[/code]

Manifest 1 testado:

[code]Manifest-Version: 1.0
Class-Path: .
Main-Class: main.TCheckSearch

Name: oracle/sql/converter_xcharset/
Sealed: false

Name: oracle/sql/
Sealed: false

Name: oracle/sql/converter/
Sealed: false

[/code]
Manifest 2 testado:

Manifest-Version: 1.0 Class-Path: . RelationalDaoSplited2_lib/ojdbc5.jar RelationalDaoSplite d2_lib/junit-4.7.jar Main-Class: main.TCheckSearch

Brilhante! Agora preciso ver uma forma dinâmica de pegar o nome da package da classe. Sabe como fazer isso?

Melhor ainda:

Tendo como premissa que a classe que faz a carga das classes anotadas NUNCA estará no mesmo pacote das classes que ela mapeará, o if se transforma nisso:

if (".".equals(fileName)) { continue; }

Assim não preciso perder tempo p/ descobrir o nome da package :smiley:

Vlw plic_ploc e sergiotaborda pela ajuda! Vou fazer mais uns testes. Qq coisa eu posto aqui! :slight_smile:

Essa classe não prevê todos os casos.
No meu entendimento “.” equivale ao diretório atual, ou seja, quando ele encontrar “.” ele deve verificar o diretório atual.

Troque o código entre as linhas 103 e 106 do código original pelo que se segue:

if( ".".equals( fileName ) ){
  readClassDir( new URL( dirName ), classLoader );
}
else{
  fileName = dirName + "/" + fileName;   
  File file = new File( fileName );   
  if( file.exists() && file.isFile() )   
    readJar( file, classLoader );   
}

Não testei o código.

Pronto, trecho substituído. Só faltou indicar o “file:/” na URL, como abaixo.

if (".".equals(fileName)) {
    readClassDir(new URL(String.format("file:/%s", dirName)), classLoader);
} else {
    fileName = dirName + "/" + fileName;
    File file = new File(fileName);
    if (file.exists() && file.isFile()) readJar(file, classLoader);
}

Código testado e funcionando! Obrigado!