Java Reflection

Olá

Antes de apresentar a dúvida, que passou a ser um problema, acho que é bom explicar o que quero fazer.

Montei uma classe em Java que me retorna uma tabela em HTML, informo a coleção e seto o header (th) e passo o método que deve ser chamado na entidade que esta na coleção através de reflection, até ai ok, já está funcionando, diríamos que é uma classe para evitar a criação de tabela na mão.

Ficou super prático e funcional, o objetivo é agilizar o desempenho no processo de desenvolvimento e poder customizar bem a tabela, tarefa que é possível, algo que não consegui com algumas bibliotecas que utilizei como displaytag (apesar de não ter mexido muito com a mesma).

Mas o meu objetivo aqui não é discutir “o como” e opções, um outro tópico seria o mais interessante.

Vamos a dúvida/problema.

Estou utilizando o código abaixo, que foi retirado de um ótimo tutorial que foi postado aqui mesmo no GUJ pelo Daniel Destro http://www.guj.com.br/java.tutorial.artigo.10.1.guj para ilustrar o problema.

Ao rodar este código, temos acesso a informações sobre a classe, no meu caso, Modulo tem uma propriedade de um determinado tipo, exemplo Modulo, ao chamar o método getModulo através do invoke, tenho o resultado do toString, método que foi implementado em Pessoa, porém, quero chamar o getNome que existe na propriedade do tipo Modulo.

public class Classe1 {

	public static void main(String args[]) {
		try {
			
			Modulo moduloPai = new Modulo();
			moduloPai.setNome("Modulo pai nome");
			
			Modulo modulo = new Modulo();
			modulo.setNome("Nome do módulo");
			modulo.setModuloPai(moduloPai);
			
			Class cls = Class.forName("Modulo");			
			Method meth1 = cls.getMethod("getNome");
			Method meth2 = cls.getMethod("getModuloPai");
			
			Object retobj1 = meth1.invoke(modulo);
			Object retobj2 = meth2.invoke(modulo);
			
			System.out.println(retobj1);
			System.out.println(retobj2);
			
		}
		catch (Throwable e) {
			System.err.println(e);
		}
	}
}

resultado ao rodar o código acima

Nome do módulo
ID: 0 NOME: Modulo pai nome URL: null ATIVO: false NIVEL: null MODULO PAI:

Pois se coloco getPessoa().getNome é lançado uma exception

java.lang.NoSuchMethodException: Modulo.getModuloPai().getNome()

Sugestões?

[code]public class Classe1 {

public static void main(String args[]) {
	try {
		
		Modulo moduloPai = new Modulo();
		moduloPai.setNome("Modulo pai nome");
		
		Modulo modulo = new Modulo();
		modulo.setNome("Nome do módulo");
		modulo.setModuloPai(moduloPai);
		
		Class cls = Class.forName("Modulo");			
		Method meth1 = cls.getMethod("getNome");
		Method meth2 = cls.getMethod("getModuloPai");
		
		Object retobj1 = meth1.invoke(modulo);
		Object retobj2 = meth1.invoke(moduloPai);
		
		System.out.println(retobj1);
		System.out.println(retobj2);
		
	}
	catch (Throwable e) {
		System.err.println(e);
	}
}

}[/code]

ou

[code]public class Classe1 {

public static void main(String args[]) {
	try {
		
		Modulo moduloPai = new Modulo();
		moduloPai.setNome("Modulo pai nome");
		
		Modulo modulo = new Modulo();
		modulo.setNome("Nome do módulo");
		modulo.setModuloPai(moduloPai);
		
		Class cls = Class.forName("Modulo");			
		Method meth1 = cls.getMethod("getNome");
		Method meth2 = cls.getMethod("getModuloPai");
		
		Object retobj1 = meth1.invoke(modulo);
		Object retobj2 = meth1.invoke((Modulo)meth2.invoke(modulo));
		
		System.out.println(retobj1);
		System.out.println(retobj2);
		
	}
	catch (Throwable e) {
		System.err.println(e);
	}
}

}[/code]

Se vc kiser acesso o pai pelo método get, então use a segunda opção. Testa aí e veja se funfa…

Também não vi referência nenhuma no código da classe Pessoa que vc comenta, então não posso falar nada sobre isso…

Já tive a mesma idéia que voce… e essa é a classe que via Reflection acessa do modo que voce quer…

Tambem escrevi os setters

E com um main de teste para voce ver como funciona

EDIT
Postei modificado

Então renzonuccitelli

Fiz o que vc indicou e não funcionou, valeu, estou atrás da solução.

E nem viu minha solução?

Acho que voce vai querer mais pra frente fazer isso

por exemplo

se o método a buscar é getPai().getNome();
Apenas passar pai.nome para o resolver e voce tera como acessar o getter e o setter do nome do pai.

E eu tinha esquecido de postar o formatter do exemplo acima.
E ela é simplesmente isso:

package mark.utils.bean;

public interface Formatter {
	public String format(Object obj);
	public Object parse(String obj);
}

Caso o valor do reflection não seja String voce pode usar o Formatter para converter uma String para o objeto e pegar um objeto e teronar uma String.

[quote=Mark_Ameba]E nem viu minha solução?

Acho que voce vai querer mais pra frente fazer isso

por exemplo

se o método a buscar é getPai().getNome();
Apenas passar pai.nome para o resolver e voce tera como acessar o getter e o setter do nome do pai.

E eu tinha esquecido de postar o formatter do exemplo acima.
E ela é simplesmente isso:

package mark.utils.bean;

public interface Formatter {
	public String format(Object obj);
	public Object parse(String obj);
}

Caso o valor do reflection não seja String voce pode usar o Formatter para converter uma String para o objeto e pegar um objeto e teronar uma String.[/quote]

Olá Mark

Vi sim, até apliquei e funcionou que foi uma beleza.

Quanto ao Formatter, comentei as linhas e fiz a conversão para String na mão, no retorno de fieldResolver.getValue(methobj), funcionou sim.

Já acessei um get de um atributo igual ao exemplo que você citou (pai.nome) ficou show.

Só uma dúvida, minhas classes de entidades não tem o método setId, porém percebi que ao acessar um objeto.id, ele faz algum tipo de acesso ao set, não entendi pq, na verdade não parei para mexer ainda, apenas fui na minha classe e coloquei o setId para testar e foi.

Saberia me dizer pq preciso ter o set sendo que quero apenas o get?

Ahh… eu escrevi essa classe para acessar o get e o set da propriedade(Para fazer uns Binds), mas voce pode tirar essa parte do construtor.

setter = new SetterHolder(getSetterMethod(classesTrace.get(classesTrace   
                .size() - 2), trace[trace.length - 1]));   

Ou então fazer um try/catch caso o setter não seja encontrado.

Refatorei essa classe quando tive tempo e pra quem quiser…

E com a grande diferença de não usar mais os métodos getter e setter dos atributos e sim acessando diretamento o campo.

EDIT

Postei modificado

Toma cuidado com isso. As vezes o pessoal coloca alguma validação ou outra tarefa qualquer num método getter ou settter e vc vai criar problemas se acessar direto o campo. Vc poderia deixar o acesso via campo ou métodos de acesso configurável…

Pra falar a verdade eu tinha pensado nisso…

Mas vou misturar os dois que já fiz e dar essa opção.

Fiz algumas alterações agora tem a opção de escolher acessar via getter/setter ou pelo Field. E ainda é possivel fazer a propria implementação extendendo

package mark.utils.el;

import mark.utils.bean.Formatter;
import mark.utils.el.handler.FieldAccessHandler;
import mark.utils.el.handler.FieldHandler;
import mark.utils.el.handler.MethodHandler;

public class FieldResolver {
	private String fieldName;
	private String name;
	private Formatter formatter;
	private FieldAccessHandler method;

	public FieldResolver(Class<?> clazz, String fieldName, String name) {
		this(clazz, fieldName, name, null);
	}

	public FieldResolver(Class<?> clazz, String fieldName) {
		this(clazz, fieldName, "", null);
	}

	public FieldResolver(Class<?> clazz, String fieldName, Handler fac) {
		this(clazz, fieldName, "", fac);
	}

	public FieldResolver(Class<?> clazz, String fieldName, String name,
			Handler fac) {
		if (fac == null)
			fac = FIELD_HANDLER;

		this.fieldName = fieldName;
		this.name = name;

		method = fac.getHandler();
		method.resolveField(clazz, fieldName);

		setFormatter(BASIC_FORMATTER);
	}

	public void setFormatter(Formatter formatter) {
		if (formatter == null)
			throw new IllegalArgumentException("Formatter can't be null!");
		this.formatter = formatter;
	}

	public void setValue(Object t, Object value) {
		method.setValue(t, value, formatter);
	}

	public Object getValue(Object t) {
		return method.getValue(t, formatter);
	}

	public String getName() {
		return name;
	}

	public Class<?> getFieldType() {
		return method.getFieldType();
	}

	public String getFieldName() {
		return fieldName;
	}

	public static final Formatter BASIC_FORMATTER = new Formatter() {
		@Override
		public String format(Object obj) {
			if (obj == null)
				return "";

			return obj.toString();
		}

		@Override
		public Object parse(String obj) {
			return obj;
		}
	};

	public static interface Handler {
		public FieldAccessHandler getHandler();
	}

	public static final Handler FIELD_HANDLER = new Handler() {
		@Override
		public FieldAccessHandler getHandler() {
			return new FieldHandler();
		}
	};

	public static final Handler METHOD_HANDLER = new Handler() {
		@Override
		public FieldAccessHandler getHandler() {
			return new MethodHandler();
		}
	};
}

FieldAcessHandler e implementações.

package mark.utils.el.handler;

import mark.utils.bean.Formatter;

public interface FieldAccessHandler {
	public void setValue(Object t, Object value, Formatter formatter);

	public Object getValue(Object t, Formatter formatter);

	public void resolveField(Class<?> clazz, String expression);

	public Class<?> getFieldType();
}
package mark.utils.el.handler;

import java.lang.reflect.Field;
import java.util.LinkedList;

import mark.utils.bean.Formatter;

public class FieldHandler implements FieldAccessHandler {
	private LinkedList<Class<?>> classesTrace;
	private LinkedList<Field> fields;

	public FieldHandler() {
		classesTrace = new LinkedList<Class<?>>();
		fields = new LinkedList<Field>();
	}

	@Override
	public Object getValue(Object t, Formatter formatter) {
		if (t == null)
			return null;
		Object obj = null;
		try {
			obj = t;
			for (int i = 0; i < fields.size(); i++)
				obj = fields.get(i).get(obj);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		return formatter.format(obj);
	}

	@Override
	public void resolveField(Class<?> clazz, String expression) {
		classesTrace.add(clazz);
		String[] trace = expression.split("[.]");

		for (int i = 0; i < trace.length; i++)
			addField(trace[i]);
	}

	@Override
	public void setValue(Object t, Object value, Formatter formatter) {
		if (t == null)
			return;
		Object obj = null;
		Field field = null;
		try {
			obj = t;
			int size = fields.size() - 2;
			if (size > -1) {
				for (int i = 0; i <= size; i++)
					obj = fields.get(i).get(obj);
				field = fields.get(size + 1);
			} else
				field = fields.get(0);

			field.set(obj, formatter.parse(value.toString()));
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void addField(String fieldName) {
		Class<?> clazz = classesTrace.get(classesTrace.size() - 1);
		try {
			Field f = getAcessibleField(clazz, fieldName);
			classesTrace.add(f.getType());
			fields.add(f);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private Field getAcessibleField(Class<?> clazz, String fieldName) {
		Field f = null;
		try {
			f = clazz.getDeclaredField(fieldName);
			f.setAccessible(true);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return f;
	}

	@Override
	public Class<?> getFieldType() {
		return fields.getLast().getType();
	}
}
package mark.utils.el.handler;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import mark.utils.bean.Formatter;

public class MethodHandler implements FieldAccessHandler {

	private List<HolderGetterValue> holders;
	private List<Class<?>> classesTrace;
	private SetterHolder setter;

	public MethodHandler() {
		classesTrace = new ArrayList<Class<?>>();
		holders = new ArrayList<HolderGetterValue>();
	}

	@Override
	public Class<?> getFieldType() {
		return holders.get(holders.size() - 1).getter.getReturnType();
	}

	@Override
	public Object getValue(Object t, Formatter formatter) {
		if (t == null)
			return null;
		Object obj = null;
		try {
			obj = holders.get(0).getValue(t);
			for (int i = 1; i < holders.size(); i++)
				obj = holders.get(i).getValue(obj);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (formatter != null)
			return formatter.format(obj);
		else
			return obj;
	}

	@Override
	public void resolveField(Class<?> clazz, String expression) {
		classesTrace.add(clazz);
		String[] trace = expression.split("[.]");
		for (int i = 0; i < trace.length; i++)
			addField(trace[i]);
		if (holders.size() != trace.length) {
			holders.clear();
			classesTrace.clear();
			throw new RuntimeException("Impossible to resolve field.");
		}
		setter = new SetterHolder(getSetterMethod(classesTrace.get(classesTrace
				.size() - 2), trace[trace.length - 1]));
	}

	@Override
	public void setValue(Object t, Object value, Formatter formatter) {
		if (t == null)
			return;
		Object obj = null;
		try {
			obj = t;
			int size = holders.size() - 2;
			if (size > -1)
				for (int i = 0; i <= size; i++)
					obj = holders.get(i).getValue(obj);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}

		setter.setValue(obj, value);
	}

	private void addField(String fieldName) {
		Class<?> clazz = classesTrace.get(classesTrace.size() - 1);
		try {
			Field next = null;
			try {
				next = clazz.getDeclaredField(fieldName);
			} catch (Exception e) {
			}
			Method m = getGetterMethod(clazz, fieldName, next);
			classesTrace.add(m.getReturnType());
			holders.add(new HolderGetterValue(m));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private Method getSetterMethod(Class<?> clazz, String fieldName) {
		Method m = null;
		try {
			try {
				m = clazz.getMethod("set"
						+ String.valueOf(fieldName.charAt(0)).toUpperCase()
						+ fieldName.substring(1), classesTrace.get(classesTrace
						.size() - 1));
			} catch (Exception e) {
				e.printStackTrace();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return m;
	}

	private Method getGetterMethod(Class<?> clazz, String fieldName, Field field) {
		Method m = null;
		try {
			if (field == null) {
				try {
					m = clazz.getMethod("get"
							+ String.valueOf(fieldName.charAt(0)).toUpperCase()
							+ fieldName.substring(1));
				} catch (Exception e) {
					e.printStackTrace();
				}
			} else if (field.getType().isAssignableFrom(Boolean.class))
				m = clazz.getMethod("is"
						+ String.valueOf(fieldName.charAt(0)).toUpperCase()
						+ fieldName.substring(1));
			else
				m = clazz.getMethod("get"
						+ String.valueOf(fieldName.charAt(0)).toUpperCase()
						+ fieldName.substring(1));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return m;
	}

	private static class HolderGetterValue {
		private Method getter;

		public HolderGetterValue(Method getter) {
			this.getter = getter;
		}

		public Object getValue(Object obj) {
			Object value = null;
			try {
				value = getter.invoke(obj);
			} catch (Exception e) {
				e.printStackTrace();
			}
			return value;
		}
	}

	private static class SetterHolder {
		private Method setter;

		public SetterHolder(Method setter) {
			this.setter = setter;
		}

		public void setValue(Object obj, Object value) {
			try {
				setter.invoke(obj, value);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}