package ilarkesto.di;
import ilarkesto.base.Reflect;
import ilarkesto.core.scope.In;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Set;
/**
* Utility class for autowiering
*
* @author wko
*/
public class Autowire {
/**
* Autowire the class <code>clazz</code> with beans provided by <code>beanProvider</code>. Use the given
* <code>objectStringMapper</code> to convert from/to strings if required. For each bean provided by
* <code>beanProvider</code> a <em>static</em> setter is called on the given class <code>clazz</code>.
*
* @param objectStringMapper optional
* @return the given <code>clazz</code>
*/
public static <T> Class<T> autowireClass(Class<T> clazz, BeanProvider beanProvider,
ObjectStringMapper objectStringMapper) {
Set<String> availableBeansNames = beanProvider.beanNames();
Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
if (!Modifier.isStatic(methods[i].getModifiers())) continue;
String methodName = methods[i].getName();
if (!methodName.startsWith("set")) continue;
String name = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
if (availableBeansNames.contains(name)) {
invokeSetter(null, methods[i], beanProvider.getBean(name), objectStringMapper);
} else if ("beanProvider".equals(name)) {
invokeSetter(null, methods[i], beanProvider, objectStringMapper);
} else {
// TODO find bean by type
}
}
Class<? extends Object> superclass = clazz.getSuperclass();
if (superclass != null && !Object.class.equals(superclass))
autowireClass(superclass, beanProvider, objectStringMapper);
return clazz;
}
/**
* Autowire the object <code>bean</code> with beans provided by <code>beanProvider</code>. Use the given
* <code>objectStringMapper</code> to convert from/to strings if required. For each bean provided by
* <code>beanProvider</code> a setter is called on the given object <code>bean</code>.
*
* @param objectStringMapper optional
* @return the given <code>bean</code>
*/
public static <T> T autowire(T bean, final BeanProvider beanProvider, ObjectStringMapper objectStringMapper) {
// Logger.DEBUG("***** autowiring", "<" + Utl.toStringWithType(bean) + ">", "with", "<"
// + Utl.toStringWithType(beanProvider) + ">");
final Set<String> availableBeanNames = beanProvider.beanNames();
Class beanClass = bean.getClass();
BeanInfo beanInfo;
try {
beanInfo = Introspector.getBeanInfo(beanClass);
} catch (IntrospectionException ex) {
throw new RuntimeException(ex);
}
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
if (propertyDescriptors != null) {
for (int i = 0; i < propertyDescriptors.length; i++) {
PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
if (propertyDescriptor != null) {
String name = propertyDescriptor.getName();
// if ("parentContext".equals(name)) continue;
Method writeMethod = propertyDescriptor.getWriteMethod();
if (writeMethod != null) {
if (writeMethod.getAnnotation(AutowireHostile.class) != null) continue;
if (availableBeanNames.contains(name)) {
invokeSetter(bean, writeMethod, beanProvider.getBean(name), objectStringMapper);
} else if ("beanProvider".equals(name)) {
invokeSetter(bean, writeMethod, beanProvider, objectStringMapper);
}
}
}
}
}
Reflect.processAnnotations(bean, new Reflect.FieldAnnotationHandler() {
@Override
public void handle(Annotation annotation, Field field, Object object) {
if (!(annotation instanceof In)) return;
String name = field.getName();
if (!availableBeanNames.contains(name)) return;
field.setAccessible(true);
try {
field.set(object, beanProvider.getBean(name));
} catch (Exception ex) {
throw new RuntimeException("Setting field " + object.getClass().getSimpleName() + "." + name
+ " failed.", ex);
}
}
});
return bean;
}
// --- helper ---
private static void invokeSetter(Object bean, Method method, Object value, ObjectStringMapper objectStringMapper) {
try {
method.invoke(bean, createWriteMethodArguments(method, value, objectStringMapper));
} catch (Throwable ex) {
throw new RuntimeException("Invoking setter '" + method.getDeclaringClass().getSimpleName() + "."
+ method.getName() + "' on '" + bean + "' with '" + value + "' failed.", ex);
}
}
private static Object[] createWriteMethodArguments(Method method, Object value,
ObjectStringMapper objectStringMapper) throws IllegalAccessException, ClassCastException {
try {
if (value != null) {
Class[] types = method.getParameterTypes();
if (types != null && types.length > 0) {
Class paramType = types[0];
if (!paramType.isAssignableFrom(value.getClass())) {
if (objectStringMapper != null && value instanceof String
&& objectStringMapper.isTypeSupported(paramType)) {
value = objectStringMapper.stringToObject((String) value, paramType);
} else {
value = convertType(paramType, value);
}
}
}
}
Object[] answer = { value };
return answer;
} catch (InvocationTargetException e) {
throw new IllegalArgumentException(e);
} catch (InstantiationException e) {
throw new IllegalArgumentException(e);
}
}
private static Object convertType(Class newType, Object value) throws InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// try call constructor
Class[] types = { value.getClass() };
try {
Constructor constructor = newType.getConstructor(types);
Object[] arguments = { value };
return constructor.newInstance(arguments);
} catch (NoSuchMethodException e) {
// try using the transformers
ITransformer transformer = getTypeTransformer(newType);
if (transformer != null) { return transformer.transform(value); }
return value;
}
}
private static ITransformer getTypeTransformer(Class aType) {
return (ITransformer) defaultTransformers.get(aType);
}
// --- dependencies ---
public static HashMap defaultTransformers = new HashMap();
static {
defaultTransformers.put(Boolean.TYPE, new ITransformer() {
public Object transform(Object input) {
return Boolean.valueOf(input.toString());
}
});
defaultTransformers.put(Character.TYPE, new ITransformer() {
public Object transform(Object input) {
return new Character(input.toString().charAt(0));
}
});
defaultTransformers.put(Byte.TYPE, new ITransformer() {
public Object transform(Object input) {
return Byte.valueOf(input.toString());
}
});
defaultTransformers.put(Short.TYPE, new ITransformer() {
public Object transform(Object input) {
return Short.valueOf(input.toString());
}
});
defaultTransformers.put(Integer.TYPE, new ITransformer() {
public Object transform(Object input) {
return Integer.valueOf(input.toString());
}
});
defaultTransformers.put(Long.TYPE, new ITransformer() {
public Object transform(Object input) {
return Long.valueOf(input.toString());
}
});
defaultTransformers.put(Float.TYPE, new ITransformer() {
public Object transform(Object input) {
return Float.valueOf(input.toString());
}
});
defaultTransformers.put(Double.TYPE, new ITransformer() {
public Object transform(Object input) {
return Double.valueOf(input.toString());
}
});
}
public interface ITransformer {
public Object transform(Object input);
}
}