Mostrando postagens com marcador JVM. Mostrar todas as postagens
Mostrando postagens com marcador JVM. Mostrar todas as postagens

terça-feira, 4 de agosto de 2020

Criando arquivo .sh para executar aplicação Java com passagem de parâmetros

Conforme mostrado no artigo http://www.josepojr.com/2020/07/criando-arquivo-bat-para-executar.html
a configuração para Windows, mas para Linux, temos pequenas mudanças conforme exemplo abaixo:


java -Dfile.encoding=UTF-8 -cp ".:libs/*" pacote.minha.classe.Main $*

Aonde "$*" permite que passamos parâmetros dentro da execução do script .sh no Linux.
Também, alteramos o símbolo separador de path do windows que é um ponto-e-vírgula ";" para dois pontos ":" e colocamos aspas no valor do parâmetro "cp".
Importante não deixa espaço em branco depois de "$*" para a aplicação Java não entender que aquele espaço em branco é um argumento passado para o programa.

Criando shell script para executar aplicações Java no Linux com dependências

INTRODUÇÃO


Nessa publicação explicaremos como criar o arquivo .sh para iniciar uma aplicação Java com as suas dependências (Pastas libs contendo Jars, por exemplo). Para isto, vamos criar uma aplicação simples chamada "ExemploJava".


CONFIGURAÇÃO


Essa aplicação terá uma pasta chamada "libs" para conter os jars de bibliotecas de terceiros que a aplicação pode usar (ou não) e será referenciada no classpath da aplicação Java.

Outra pasta chamada de "bin" conterá os pacotes e classes do projeto. Por exemplo, se tivermos uma classe fictícia chamada "A" dentro do pacote "a.b.c", portanto o nome completo da classe será "a.b.c.A". E dentro da pasta "bin", teremos a pasta "a" e dentro desta a pasta "b". E por último, dentro da pasta "b", teremos a pasta "c" que conterá o arquivo "A.java" definindo a classe pública "A".

Estas duas pastas estarão dentro da pasta "ExemploJava" e o script .sh ("exemploJava.sh" por exemplo) estará dentro desta pasta.

Para efeito prático, vamos adotar a classe "A" como a classe com o método main (inicializadora da aplicação Java).

Portanto, o conteúdo do arquivo .sh será:

java -Dfile.encoding=UTF-8 -cp ".:bin/:libs/*:" a.b.c.A 


Aonde temos:

-Dfile.encoding=UTF-8
aonde informa a codificação usada, muito recomendável usar unicode no UTF-8 pois é uma codificação internacional e preparada para lidar com diversos alfabetos e idiomas.


-cp ".:bin/:libs/*:"
aonde informamos as pastas, jars e arquivos .class que estarão no classpath. Estando no classpath, nossa aplicação pode carregar e usar as classes. Se não estiver no classpath, pode dar erros em tempo de execução, lançado exceções dos tipos "java.lang.ClassNotFoundException" ou "java.lang.NoClassDefFoundError" por exemplo. Além disso, se forem colocados paths (caminhos) errados, ao rodar a aplicação não dará erro a priori (por causa desses paths sem jars ou arquivos .class), mas quando a classe Main for executada e for carregando as dependências, pode chegar numa classe que não estará no classpath. Portanto, muito cuidado para colocar o caminho correto para que as dependências dentro desses caminhos sejam carregadas corretamente para dentro da JVM.

Nesse exemplo, usamos a pasta bin para conter os arquivos .class ou recursos. Mas também é possível empacotar tudo num jar e colocar nesse argumento diretamente (":meuarquivo.jar") ou colocar esse jar na pasta "bin" ou "libs".
Lembrando também que os dois-pontos ":" é o separador de paths (caminhos) entre um jar ou diretório do outro. O sinal ponto "." representa o diretório atual que o script está sendo rodado.


a.b.c.A
É o nome completo para a classe Main que será iniciada pela JVM no comando "java".
Sem essa classe Main, o comando "java" abortará, mostrando uma mensagem de erro dizendo que não foi informado uma classe com o método estático "void main(String args[])".


OBSERVAÇÃO


Muito importante, depois da classe main "A", deverá ter um espaço em branco!
Caso contrário, ao executar o script sh, dará erros dizendo que a classe "a.b.c.A" não foi localizada e nem carregada! Pois, o shell script fornecerá como parâmetro a classe de nome completo "a.b.c.A\n" com a QUEBRA DE LINHA inclusa! E logicamente, no nosso classpath (formado pelo núcleo do Java e mais as pastas informadas no parâmetro "cp") essa classe de nome completo "a.b.c.A\n" não existirá! E pior, difícil de localizar esse tipo de erro, pois a quebra de linha "\n" fica invisível no terminal!

domingo, 19 de setembro de 2010

Execução de Classes Java via API de Reflexão.

INTRODUÇÃO

Nesse artigo, mostraremos como executar uma instância de uma classe Java usando a API de Reflexão do Java. Partiremos de um arquivo Java compilador (".class") passado como parâmetro. Esse arquivo ".class" será carregado por um carregador de classes personalizado, chamado de ClassLoader. Nesse caso, extenderemos a classe java.lang.ClassLoader para criarmos o nosso carregador de classes especifico com o nome de MyClassLoader. Durante a execução da aplicação, depois de carregado o arquivo ".class" passado como parâmetro, usaremos a API de Reflexão para executar um método estático dessa classe.


CARREGADOR DE CLASSES

Primeiramente, vamos criar um ClassLoader personalizado, chamado "MyClassLoader.java", veja a seguir:

Arquivo "MyClassLoader.java"
--------------------------------------------------------------------------------------------------------

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

public class MyClassLoader extends ClassLoader {

    private static final int MASK = 0x000000FF;
    private static final int MAGIC_CLASS = 0xCAFEBABE;

    private static final int CONSTANT_UTF8_INFO = 1;
    private static final int CONSTANT_INTEGER_INFO = 3;
    private static final int CONSTANT_FLOAT_INFO = 4;
    private static final int CONSTANT_LONG_INFO = 5;
    private static final int CONSTANT_DOUBLE_INFO = 6;
    private static final int CONSTANT_CLASS_INFO = 7;
    private static final int CONSTANT_STRING_INFO = 8;
    private static final int CONSTANT_FIELDREF_INFO = 9;
    private static final int CONSTANT_METHODREF_INFO = 10;
    private static final int CONSTANT_INTERFACE_METHODREF_INFO = 11;
    private static final int CONSTANT_NAMEANDTYPE_INFO = 12;

    private Map> mapClasses = new HashMap>();

    public MyClassLoader() {

    }

    /**
     * @param name
     *            Nome da Classe ou Nome do Recurso.
     */
    protected synchronized Class loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        // Verifica se a classe já foi carregada
        Class c1 = mapClasses.get(name);

        if (c1 == null) {
            // Nova classe.

            // Retorna se for uma classe de sistema.
            try {
                return findSystemClass(name);

            } catch (ClassNotFoundException e) {
            }

            // Carrega os bytes através do nome do recurso.
            byte[] classBytes = loadClassBytes(name);

            if (classBytes == null) {
                throw new ClassNotFoundException(name);
            }

            // Através do bytecodes, descobrir o nome da classe!
            String className = getClassName(classBytes);

            c1 = defineClass(className, classBytes, 0, classBytes.length);

            if (c1 == null) {
                throw new ClassNotFoundException(name);
            }

            // Nome do recurso - Classe.
            mapClasses.put(name, c1);

            // Nome da classe - Classe.
            mapClasses.put(className, c1);

            if (resolve) {
                resolveClass(c1);
            }
        }

        return c1;
    }

    /**
     * Implementação padrão.
     * 
     * Procurar dentro da pasta scripts. Usar caminho relativo.
     * 
     * 
     */
    private byte[] loadClassBytes(String name) {
        try {
            FileInputStream in = new FileInputStream(name);

            if (in != null) {
                ByteArrayOutputStream buffer = new ByteArrayOutputStream();

                int ch = in.read();

                while (ch != -1) {
                    buffer.write((byte) ch);

                    ch = in.read();
                }

                in.close();

                return buffer.toByteArray();
            }

        } catch (IOException e) {
        }

        return null;
    }

    /**
     * Localizar nome de arquivo através dos bytecodes.
     * 
     */
    private String getClassName(byte b[]) {
        // return "Test";
        int j = 0;

        Map mapIndexAndObject = new HashMap();

        int aux = (MASK & b[j]) << 24;
        aux = aux | (MASK & b[j + 1]) << 16;
        aux = aux | (MASK & b[j + 2]) << 8;
        aux = aux | MASK & b[j + 3];

        if (aux != MAGIC_CLASS) {
            throw new IllegalArgumentException("O arquivo class inválido! ");
        }

        int constant_pool_count = (MASK & b[j + 8]) << 8 | MASK & b[j + 9];

        j = j + 10;

        for (int i = 1; i < constant_pool_count; i++) {

            switch (MASK & b[j]) {
            case CONSTANT_UTF8_INFO: {
                j++;

                int length = (MASK & b[j + 0]) << 8 | MASK & b[j + 1];

                j = j + 2;

                Reader reader;

                try {
                    reader = new InputStreamReader(new ByteArrayInputStream(b,
                            j, length), "UTF-8");
                } catch (UnsupportedEncodingException e1) {
                    e1.printStackTrace();

                    // Usa o padrão (UTF-16) se não existir.
                    reader = new InputStreamReader(new ByteArrayInputStream(b,
                            j, length));
                }

                j = j + length;

                char cbuf[] = new char[length];
                try {
                    reader.read(cbuf);

                } catch (IOException e) {
                    e.printStackTrace();
                }

                mapIndexAndObject.put(i, new String(cbuf));

                try {
                    reader.close();

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
                ;
                break;
            case CONSTANT_INTEGER_INFO: {
                j = j + 5;
            }
                ;
                break;
            case CONSTANT_FLOAT_INFO: {
                j = j + 5;

            }
                ;
                break;
            case CONSTANT_LONG_INFO: {
                j = j + 9; 

                i++;
            }
                ;
                break;
            case CONSTANT_DOUBLE_INFO: {
                j = j + 9; 

                i++;
            }
                ;
                break;
            case CONSTANT_CLASS_INFO: {
                j++;

                aux = (MASK & b[j + 0]) << 8 | MASK & b[j + 1];

                mapIndexAndObject.put(i, aux);

                j = j + 2;
            }
                ;
                break;
            case CONSTANT_STRING_INFO: {
                j = j + 3;
            }
                ;
                break;
            case CONSTANT_FIELDREF_INFO: {
                j = j + 5;

            }
                ;
                break;
            case CONSTANT_METHODREF_INFO: {
                j = j + 5;

            }
                ;
                break;
            case CONSTANT_INTERFACE_METHODREF_INFO: {
                j = j + 5;

            }
                ;
                break;
            case CONSTANT_NAMEANDTYPE_INFO: {
                j = j + 5;

            }
                ;
                break;
            }
        }

        j = j + 2;

        aux = (MASK & b[j + 0]) << 8 | MASK & b[j + 1];

        int index = (Integer) mapIndexAndObject.get(aux);

        return (String) mapIndexAndObject.get(index);
    }
}

--------------------------------------------------------------------------------------------------------

Nessa implementação, vamos sobrescrever o seguinte método da classe ClassLoader: 

protected synchronized Class loadClass(String name, boolean resolve)  throws ClassNotFoundException

Esse método recebe como parâmetro o nome qualificado (Pacote + Nome da Classe) da classe a ser carregada. O objetivo desse método é apresentar essa classe a JVM com a chamada ao seguinte método:

protected final Class defineClass(String name, byte[] b, int off, int len) throws ClassFormatError

E também, o ClassLoader personalizado deve administrar as classes que foram carregadas, através do uso de um mapa (Objeto cuja classe implementa a interface java.util.Map) relacionando o nome da classe com a classe carregada, por exemplo. 
Se caso a classe carregada for classe do sistema ou da API do Java, é chamado o seguinte método:

protected final Class findSystemClass(String name) throws ClassNotFoundException

Esse método deve ser executado no começo do processo de carregamento de uma classe, se está for de sistema, o ClassLoader personalizado não deve carregá-la e esse método retornará a classe do sistema já carregada. Se a classe não for do sistema, será lançada a exceção java.lang.ClassNotFoundException com o intuito de indicar que a classe não foi localizada. Nesse caso, o nosso ClassLoader não deve tratar a exceção e partir para o processo de carregamento da classe em questão.

Se a flag "resolve" do método "loadClass(String name, boolean resolve)" estiver configurada com Verdadeiro, esse método deve chamar o seguinte método da superclasse:

protected final void resolveClass(Class c) 

Esse método carregará as outras classes que a classe carregada depende. Mas a chamadas ao método "loadClass(String name, boolean resolve)" são feitas pela JVM e não pela aplicação. 


API DE REFLEXÃO JAVA

Nesse momento já temos o nosso carregador de classes personalizado. A seguir temos um programa que lê da linha de comandos o arquivo ".class" a ser carregado pelo nosso carregador e depois, de posse com o objeto da classe java.lang.Class localizamos o método estático "main" da classe carregada, chamando o seguinte método:

Method m = c.getMethod("main", new Class[] { args.getClass() });

A variável "c" é a classe carregada contendo o método estático "main" cujo argumentos é um array de java.lang.String!
Nesse chamada, temos o método a ser invocado pela chamada a seguir:

m.invoke(null, new Object[] { args });

Essa chamada invocará o método localizado na classe carregada!
A seguir, temos o programa que fará essa execução:

Arquivo "ExecutionTest.java"
--------------------------------------------------------------------------------------------------------
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ExecutionTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        if (args.length < 1) {
            throw new IllegalArgumentException(
            "Deve especificar o nome do arquivo");
        }

        ClassLoader classLoader = new MyClassLoader();

        Class c = null;

        try {
            c = classLoader.loadClass(args[0]);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // Faz invocação do método estático "main"
        Method m = null;

        // Localiza método estático main.

        try {
            m = c.getMethod("main", new Class[] { args.getClass() });

        } catch (SecurityException e) {
            e.printStackTrace();

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        // Executa método estático main.
        try {
            m.invoke(null, new Object[] { args });

        } catch (IllegalArgumentException e) {
            e.printStackTrace();

        } catch (IllegalAccessException e) {
            e.printStackTrace();

        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

--------------------------------------------------------------------------------------------------------

Além desse arquivo, abaixo temos um arquivo java de teste que deve ser compilado para ser usado no programa ExecutionTest.

Arquivo "Test.java"
--------------------------------------------------------------------------------------------------------

public class Test {

    /**
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("Arquivo compilado e executado corretamente!!");
    }
}

--------------------------------------------------------------------------------------------------------

Para executar esse exemplo nesse artigo, basta compilar a classe "Test.java" e o resultado que é o arquivo "Test.class" deve ser usado como argumento na execução do programa "ExecutionTest" lembrando que o "MyClassLoader.class" deve estar no classpath desse programa.


REFERÊNCIAS


http://en.wikipedia.org/wiki/Java_Classloader

http://www.javaworld.com/javaworld/jw-10-1996/jw-10-indepth.html

http://download-llnw.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html

http://download.oracle.com/javase/tutorial/reflect/

terça-feira, 7 de setembro de 2010

Formato de Arquivo Class - Java 6

INTRODUÇÃO

Nesse artigo vamos conhecer um pouco da estrutura do arquivo ".class" gerados pelos compiladores Java. Nesse arquivo contém informações a respeito da classe compilada, métodos, campos e outras referências necessárias que serão usadas pela Máquina Virtual do Java no momento do carregamento da classe.
O conhecimento a respeito da estrutura desse arquivo ".class" nos ajuda a entender melhor como os dados estão organizados de maneira que possamos fazer otimizações nesse arquivo, buscar informações, etc.

ESTRUTURA BÁSICA

Nas figuras a seguir, veremos a estrutura básica do formato de arquivo class, versão Java 6.



A estrutura do formato de arquivo class segue uma estrutura análoga as estruturas criadas na linguagem C. Nos primeiros itens às esquerdas, indicam o tipo do elemento, por exemplo, "u1", "u2", "u4" e nos itens às direitas, indicam as variáveis. Basicamente, "u1", "u2" e "u4" armazenam 1, 2 e 4 bytes sem sinal. A seguir, temos a descrição de cada item.

u4 magic: Indica o formato de arquivo class cujo valor em hexadecimal é 0xCAFEBABE.

u2 minor_version: Indica a menor versão suportada.

u2 major_version: Indica a maior versão suportada.

u2 constant_pool_count: Número de constantes armazenadas na tabela de constantes.

cp_info - Constantes: Armazena constant_pool_count - 1 constantes que representam valores constantes como cadeia de caracteres, números, nomes de classes, métodos, etc. Para cada constante, é utilizada uma estrutura especifica que será explicada mais adiante.


u2 acess_flags: Representa propriedades da classe ou interface que está sendo representada  nesse arquivo ".class". Abaixo temos alguns valores usados nesse item.


Nome - Valor - Definição

ACC_PUBLIC                - 0x0001 - Declara classe publica, pode ser acessado fora do pacote.
ACC_FINAL                   - 0X0010 - Declara classe final, subclasses não são permitidas.
ACC_SUPER                 - 0X0020 - Trata métodos da super classe quando invocado pela instrução invokespecial
ACC_INTERFACE        - 0x0200 - É uma interface, não é uma classe.
ACC_ABSTRACT         - 0X0400 - Declara que a classe é abstrata, não pode ser instânciada.
ACC_SYNTHETIC        - 0X1000 - Declara sintético, não presente no código fonte.
ACC_ANNOTATION    - 0X2000 - Declara com um tipo de anotação.
ACC_ENUM                  - 0X4000 - Declara como tipo enum


Observações:

A flag ACC_SYNTHETIC indica que a classe foi gerada pelo compilador e não tem código fonte.
A flag ACC_ENUM indica que a classe ou superclasse é declarada como tipo enum.
A flag ACC_INTERFACE configurada indica uma interface e não classe, e o contrário, indica classe.

Cada arquivo de classe não pode ter as flags ACC_FINAL, ACC_SUPER ou ACC_ENUM configurados.
A flag ACC_INTERFACE indica que é um tipo de anotação e a flag ACC_INTERFACE nesse caso, deve ser configurada.
O arquivo de classe não pode ter as flags ACC_FINAL e ACC_ABSTRACT configuradas.

u2 this_class: Indice para a tabela de constantes indicando a classe / interface em questão.

u2 super_class: Indice para a tabela de constantes indicando a super classe da classe desse arquivo em questão. Se esse valor for 0 (Zero), a super classe é java.lang.Object.

u2 interface_count: Número de interfaces implementadas (ou estendida) pela classe (ou interface).

u2 interfaces [interface_count]: Array de indices para a tabela de constantes com todas as interfaces implementadas (ou estendida) pela classe (ou interface).

u2 fields_count: Número de campos que foi definido nesse arquivo ".class".

field_info - Descrição dos Campos: Estrutura que define cada campo usado no arquivo. No total, temos um número de fields_count para essas estruturas. E a estrutura field_info, será explicada mais adiante nesse artigo.

u2 methods_count: Número de métodos que foi definido nesse arquivo ".class".

method_info - Descrição dos Métodos: Estrutura que define cada método usado no arquivo. No total, temos um número de methods_count para essas estruturas. E a estrutura method_info, será explicada mais adiante nesse artigo.

u2 attribute_count: Número de atributos que foi definido nesse arquivo ".class".

attribute_info - Descrição dos Campos: Estrutura que define cada método usado no arquivo. No total, temos um número de attribute_count para essas estruturas. E a estrutura attribute_info, será explicada mais adiante nesse artigo.


ESTRUTURA - cp_info

Agora vamos explorar a fundo a estrutura geral das constantes que será mostrada na figura a seguir:


u1 tag: Define o tipo da constante.
u1 info[]: Array de bytes armazenados pela constante. Esse array é usado de acordo com o tipo da constante, pois tem constantes que usam apenas 4 bytes e outras usam um número variável de bytes cujo tamanho é geralmente guardado na primeira posição depois da definição do tipo de constante (item tag).

Observações:
Note que não temos como saber o tamanho de bytes a seguir (depois do item tag) sem saber o tipo de constante, o que nos obriga a determinar o tipo de constante (pelo item tag) para depois definirmos com precisão a quantidade de bytes reservada para aquela constante em particular.
Para as constantes do tipo long e double, conta-se dois itens do array de constantes (estrutura cp_info). Ou seja, se a constante long possuir um indice n, o próximo indice n+1 também pertence a constante long.

No total, temos 11 tipos de constantes usadas no formato de arquivo class - Java 6 que estão descritos na figura a seguir:


Nas figuras a seguir, serão mostrados cada um dos 11 tipos de constantes existentes no arquivo class.















ESTRUTURA - field_info



u2 acess_flags: Propriedades do Campo.

Nome - Valor - Descrição

ACC_PUBLIC              - 0x0001 - Declara campo público e pode ser acessado fora do pacote.
ACC_PRIVATE            - 0x0002 - Declara campo privado, uso somente na própria classe.
ACC_PROTECTED     - 0x0004 - Declara campo protegido, uso somente na classe e subclasses.
ACC_STATIC              - 0x0008 - Declara campo estático.
ACC_FINAL                - 0x0010 - Declara campo final, não pode ser alterado depois de inicializado.
ACC_VOLATILE         - 0x0040 - Declara volatil não pode ser cacheada.
ACC_TRANSIENT     - 0x0080 - Declara como temporária, não pode ser escrita ou lida num gerenciador de objetos persistentes.
ACC_SYNTHETIC     - 0x1000 - Declara sintético, não presente no código fonte.
ACC_ENUM               - 0x4000  - Declara com um elemento enum


ESTRUTURA - method_info


u2 acess_flags: Propriedades do Método.

Nome - Valor - Descrição

ACC_PUBLIC                  - 0x0001 - Declara método público.
ACC_PRIVATE                - 0x0002 - Declara método privado.
ACC_PROTECTED         - 0x0004 - Declara método protegido.
ACC_STATIC                  - 0x0008 - Declara método estático.
ACC_FINAL                    - 0x0010 - Declara método final.
ACC_SYNCHRONIZED - 0x0020 - Declara método sincronizado.
ACC_BRIDGE                 - 0x0040 - Declara método "ponte", gerado pelo compilador.
ACC_VARARGS              - 0x0080 - Declarado com variáveis com vários argumentos.
ACC_NATIVE                  - 0x0100 - Declara native, implementando em outra linguagem além de Java.
ACC_ABSTRACT            - 0x0400 - Declara método abstrato, a implementação não é fornecida.
ACC_STRICT                  - 0x0800 - Declara strictfp - Modo de ponto flutuante é FP-strict.
ACC_SYNTHETIC          - 0x1000 - Declara sintético, não presente no código fonte.


ESTRUTURA - attribute_info



Observações:

Na estrutura attribute_info, temos os últimos itens definindo um tipo de atributo em particular. Cada tipo de atributo é usado em uma situação particular (por exemplo, dentro de métodos, campos, etc) e em cada uma dessas, a JVM deve reconhecer os atributos suportados ou ignorá-los. Como exemplo de atributos, temos: ConstantValue, Code, StackMapTable, Exceptions, Synthetic, Signature, LineNumberTable, LocalVariableTable, Deprecated, InnerClasses (Classes internas), EnclosingMethod (Classes anônimas), SourceFile, SourceDebugExtension, RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations, RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations, AnnotationDefault, etc. Veja nas referências para conhecer a fundo esses atributos.


EXEMPLO

Depois do estudo do formato de arquivo class, podemos criar uma aplicação de exemplo na qual apartir de uma classe Java compilada em forma de array de bytes, podemos extrair o nome da classe. Veja na classe Java abaixo:

Arquivo - FindClassNameTest.java
-------------------------------------------------------------------------------------------------------------------
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;


public class FindClassNameTest {

    private static final int MASK = 0x000000FF;
    private static final int MAGIC_CLASS = 0xCAFEBABE;

    private static final int CONSTANT_UTF8_INFO = 1;
    private static final int CONSTANT_INTEGER_INFO = 3;
    private static final int CONSTANT_FLOAT_INFO = 4;
    private static final int CONSTANT_LONG_INFO = 5;
    private static final int CONSTANT_DOUBLE_INFO = 6;
    private static final int CONSTANT_CLASS_INFO = 7;
    private static final int CONSTANT_STRING_INFO = 8;
    private static final int CONSTANT_FIELDREF_INFO = 9;
    private static final int CONSTANT_METHODREF_INFO = 10;
    private static final int CONSTANT_INTERFACE_METHODREF_INFO = 11;
    private static final int CONSTANT_NAMEANDTYPE_INFO = 12;

    /**
     * @param args
     */
    public static void main(String[] args) {
        if(args.length < 1) {
            throw new IllegalArgumentException("Precisa pelo menos de um nome de arquivo .class");
        }

        InputStream in = null;

        try {
            in = new FileInputStream(args[0]);

        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }

        InputStream inBuf = new BufferedInputStream(in);

        String name = null;

        try {
            name = getClassName(inBuf);

        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("O nome da classe / interface é: " + name);
    }

    public static String getClassName(final InputStream inputStream) throws IOException {
        Map mapIndexAndObject = new HashMap();

        byte b[] = new byte[10];

        inputStream.read(b, 0, b.length);

        int aux = (MASK & b[0])<<24;
        aux = aux | (MASK & b[1])<<16;
        aux = aux | (MASK & b[2])<<8;
        aux = aux | MASK & b[3];

        if(aux != MAGIC_CLASS) {
            throw new IllegalArgumentException("O arquivo class inválido! ");
        }

        int constant_pool_count = (MASK & b[8])<<8 | MASK & b[9];

        for (int i = 1; i < constant_pool_count; i++) {
            int tag = inputStream.read();

            switch(tag) {
            case CONSTANT_UTF8_INFO: {
                b = new byte[2];
                inputStream.read(b, 0, b.length);

                int length = (MASK & b[0])<<8 | MASK & b[1];

                b = new byte[length];
                inputStream.read(b, 0, b.length);

                Reader reader = new InputStreamReader(new ByteArrayInputStream(b), "UTF-8");

                char cbuf [] = new char[b.length];
                reader.read(cbuf);

                mapIndexAndObject.put(i, new String(cbuf));

                reader.close();

            }; break;
            case CONSTANT_INTEGER_INFO: {
                advanceInputStream(inputStream, 4);

            };break;
            case CONSTANT_FLOAT_INFO: {
                advanceInputStream(inputStream, 4);

            }; break;
            case CONSTANT_LONG_INFO: {
                advanceInputStream(inputStream, 8);

                i++;

            }; break;
            case CONSTANT_DOUBLE_INFO: {
                advanceInputStream(inputStream, 8);

                i++;

            }; break;
            case CONSTANT_CLASS_INFO: {
                b = new byte[2];
                inputStream.read(b, 0, b.length);

                aux = (MASK & b[0])<<8 | MASK & b[1];

                mapIndexAndObject.put(i, aux);

            }; break;
            case CONSTANT_STRING_INFO: {
                advanceInputStream(inputStream, 2);

            }; break;
            case CONSTANT_FIELDREF_INFO: {
                advanceInputStream(inputStream, 4);

            }; break;
            case CONSTANT_METHODREF_INFO: {
                advanceInputStream(inputStream, 4);

            };break;
            case CONSTANT_INTERFACE_METHODREF_INFO: {
                advanceInputStream(inputStream, 4);

            }; break;
            case CONSTANT_NAMEANDTYPE_INFO: {
                advanceInputStream(inputStream, 4);

            }; break;
            }
        }

        advanceInputStream(inputStream, 2);

        b = new byte[2];
        inputStream.read(b, 0, b.length);

        aux = (MASK & b[0])<<8 | MASK & b[1];

        int index = (Integer) mapIndexAndObject.get(aux);

        return (String) mapIndexAndObject.get(index);
    }

    /**
     * Avança InputStream sem obter os bytes.
     *
     * @param in InputStream
     * @param length Quantidade a ser avançada.
     * @throws IOException Erros de I/O.
     */
    private static void advanceInputStream(final InputStream in, final int length) throws IOException {
        for (int i = 0; i < length; i++) {
            in.read();
        }
    }
}

-------------------------------------------------------------------------------------------------------------------

Na execução do arquivo compilado Java, precisamos passar como argumento o caminho e o nome da classe ou interface na qual queremos extrair o nome partindo do array de bytes do arquivo ".class". Assim, se temos o arquivo ".class" localizado em /home/user/Test.class, então, na execução do programa teremos o seguinte comando:


java FindClassNameTest /home/user/Test.class


Mas, pode ser perguntado, porque extrair o nome da classe / interface apartir do array de byte sendo que no código temos o nome (filename) do arquivo Java ?
Sim, é verdade, podemos extrair o nome diretamente pelo caminho / nome fornecido como parâmetro na execução do interpretador Java. Mas a vantagem maior de extrair o nome da classe / interface apartir do array de bytes é que em muitas situações não temos acesso ao arquivo físico que originou o array de bytes, como o uso de Classloaders, transformação, manipulação de bytecodes, etc. Situações nas quais não temos o arquivo físico e somente o array de bytes. Consequentemente, não precisamos mais depender do arquivo físico originário do array de bytes, pois o próprio array contém todas as informações a respeito da classe compilada!


REFERÊNCIAS

http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#75883

http://jcp.org/aboutJava/communityprocess/final/jsr202/index.html

http://en.wikipedia.org/wiki/Class_%28file_format%29

http://www.murrayc.com/learning/java/java_classfileformat.shtml

http://en.wikipedia.org/wiki/Java_Virtual_Machine

http://asm.ow2.org/