package xapi.ui.autoui.impl;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import xapi.annotation.inject.InstanceOverride;
import xapi.log.X_Log;
import xapi.log.api.LogLevel;
import xapi.platform.GwtDevPlatform;
import xapi.platform.JrePlatform;
import xapi.ui.autoui.api.BeanValueProvider;
import xapi.ui.autoui.api.DoNotIndex;
import xapi.ui.autoui.api.UiOptions;
import xapi.ui.autoui.api.UiRendererOptions;
import xapi.ui.autoui.api.UiRenderingContext;
import xapi.ui.autoui.api.UserInterfaceFactory;
import xapi.util.X_Debug;
import xapi.util.api.ConvertsValue;
@JrePlatform
@GwtDevPlatform
@InstanceOverride(implFor=UserInterfaceFactory.class, priority=1)
public class UserInterfaceFactoryDefault extends AbstractUserInterfaceFactory{
private static final int MAX_DEPTH = 10;
@Override
protected UiRenderingContext[] getOptions(final Class<?> type) {
final List<UiRenderingContext> options = new ArrayList<UiRenderingContext>();
final BeanValueProvider values = getBeanProvider(type);
// Check the package for options
final Package pkg = type.getPackage();
if (pkg != null && pkg.isAnnotationPresent(UiOptions.class)) {
options.addAll(extractRenderingContext(pkg.getAnnotation(UiOptions.class), values));
}
if (type.isAnnotationPresent(UiOptions.class)) {
final UiOptions opts = type.getAnnotation(UiOptions.class);
if (opts.fields().length > 0) {
values.setChildKeys(opts.fields());
}
}
// check for enclosing types/methods?
Class<?> check = type;
while (check != null) {
// Check the type for options
addAllRendererContexts(options, check, values);
// Enclosing method not supported by GWT; given it adds excess complexity anyway,
// it will not be supported in the forseeable future
// Method enclosing = check.getEnclosingMethod();
// if (enclosing != null){
// addAllRendererContexts(options, enclosing, values);
// }
check = check.getEnclosingClass();
}
for (final Method m : type.getMethods()) {
addAllRendererContexts(options, m, values);
}
return options.toArray(new UiRenderingContext[options.size()]);
}
protected void addAllRendererContexts(final List<UiRenderingContext> options, final AnnotatedElement element, final BeanValueProvider values) {
if (element.isAnnotationPresent(UiOptions.class)) {
options.addAll(extractRenderingContext(element.getAnnotation(UiOptions.class), values));
}
if (element.isAnnotationPresent(UiRendererOptions.class)) {
options.addAll(extractRenderingContext(element.getAnnotation(UiRendererOptions.class), values,
element instanceof Method ? getNameFromMethod((Method)element) : null ));
}
}
@Override
protected void recursiveAddBeanValues(final BeanValueProvider bean, final Class<?> cls,
final ConvertsValue<Object, Object> converter, final String prefix, final int depth) {
if (depth > MAX_DEPTH) {
X_Log.warn(getClass(), "Recursion sickness detected in "+cls+" from "+prefix+"; depth reached "+MAX_DEPTH);
if (X_Log.loggable(LogLevel.TRACE)) {
X_Log.trace("Consider using the @DoNotIndex annotation in the recursion chain produced by "+prefix);
}
return;
}
// Add all method getter
for (final Method m : cls.getMethods()) {
if (
m.getParameterTypes().length == 0
&& m.getDeclaringClass() != Object.class
&& m.getReturnType() != void.class
) {
final DoNotIndex noIndex = m.getAnnotation(DoNotIndex.class);
if (noIndex != null && noIndex.unlessDepthLessThan() >= depth) {
continue;
}
final String name = getNameFromMethod(m);
final String key = prefix.length()==0 ? name : prefix+"."+name;
final Method rootMethod = getRootMethod(m);
final ConvertsValue<Object, Object> valueConverter = new ConvertsValue<Object, Object>() {
@Override
public Object convert(final Object from) {
final Object parent = converter.convert(from);
try {
return rootMethod.invoke(parent);
} catch (final Exception e) {
throw X_Debug.rethrow(e);
}
}
};
bean.addProvider(key, name, valueConverter);
if (isNotPrimitive(m.getReturnType())) {
recursiveAddBeanValues(bean, m.getReturnType(), valueConverter, key, depth+1);
}
}
}
}
protected Method getRootMethod(final Method m) {
if (m.getDeclaringClass().isInterface()) {
// When using interfaces, search for the deepest ancestor interface with the given method.
Method winner = m;
for (final Class<?> c : m.getDeclaringClass().getInterfaces()) {
try {
final Method method = c.getMethod(m.getName(), m.getParameterTypes());
if (method.getDeclaringClass().isAssignableFrom(winner.getDeclaringClass())) {
winner = method;
}
} catch (final Throwable ignored){}
}
return winner;
}
return m;
}
protected boolean isNotPrimitive(final Class<?> cls) {
return
!cls.isPrimitive()
&& cls != String.class
&& (
!cls.getPackage().getName().equals("java.lang")
||(
cls != Boolean.class
&& cls != Byte.class
&& cls != Character.class
&& cls != Short.class
&& cls != Integer.class
&& cls != Long.class
&& cls != Float.class
&& cls != Double.class
))
;
}
}