package xapi.ui.autoui.impl;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.inject.Named;
import xapi.log.X_Log;
import xapi.source.write.MappedTemplate;
import xapi.ui.autoui.api.BeanValueProvider;
import xapi.ui.autoui.api.UiOptions;
import xapi.ui.autoui.api.UiRenderer;
import xapi.ui.autoui.api.UiRendererOptions;
import xapi.ui.autoui.api.UiRendererSelector;
import xapi.ui.autoui.api.UiRenderingContext;
import xapi.ui.autoui.api.UserInterface;
import xapi.ui.autoui.api.UserInterfaceFactory;
import xapi.ui.autoui.api.Validator;
import xapi.util.X_Debug;
import xapi.util.X_Util;
import xapi.util.api.ConvertsValue;
@SuppressWarnings("rawtypes")
public abstract class AbstractUserInterfaceFactory implements UserInterfaceFactory {
@SuppressWarnings("unchecked")
@Override
public <T, U extends UserInterface<T>> U createUi(Class<? extends T> type, Class<? super U> uiType) {
UiRenderingContext[] options = getOptions(type);
List<UiRenderingContext>
head = new ArrayList<UiRenderingContext>(),
body = new ArrayList<UiRenderingContext>(),
tail = new ArrayList<UiRenderingContext>()
;
for (UiRenderingContext ctx : options) {
(ctx.isHead()?head:ctx.isTail()?tail:body).add(ctx);
}
return (U) instantiateUi((Class)type, (Class)uiType, options);
}
protected abstract UiRenderingContext[] getOptions(Class<?> type);
protected UiRenderingContext createContext(Class<? extends UiRenderer> renderer, UiRendererOptions rendererOptions) {
// The default createContext method will simply instantiate all necessary instances immediately.
// This method is left protected so you can optionally implement caching or lazy loading.
UiRenderingContext ctx = new UiRenderingContext(create(renderer));
applyOptions(ctx, rendererOptions);
return ctx;
}
protected void applyOptions(UiRenderingContext ctx, UiRendererOptions rendererOptions) {
if (rendererOptions.isHead()) {
ctx.setHead(true);
} else if (rendererOptions.isTail()) {
ctx.setTail(true);
}
if (rendererOptions.isWrapper()) {
ctx.setWrapper(true);
}
ctx.setSelector(getSelector(ctx, rendererOptions));
ctx.setValidators(getValidators(ctx, rendererOptions));
}
protected Collection<UiRenderingContext> extractRenderingContext(UiOptions annotation, BeanValueProvider bean) {
List<UiRenderingContext> ctxes = new ArrayList<UiRenderingContext>();
for (UiRendererOptions rendererOption : annotation.renderers()) {
ctxes.addAll(extractRenderingContext(rendererOption, bean, null));
}
return ctxes;
}
protected Collection<UiRenderingContext> extractRenderingContext(UiRendererOptions rendererOption, BeanValueProvider bean, String methodName) {
List<UiRenderingContext> ctxes = new ArrayList<UiRenderingContext>();
for (Class<? extends UiRenderer> renderer : rendererOption.renderers()) {
UiRenderingContext ctx = createContext(renderer, rendererOption);
ctxes.add(ctx);
final BeanValueProvider ctxBean;
if (methodName == null) {
if (rendererOption.isWrapper()) {
// We must rebase $name and $value ad-hoc for each method
ctxBean = bean.rebaseAll();
} else {
ctxBean = bean;
}
} else {
// Rebase $name and $value to match the given method
ctxBean = bean.rebase(methodName);
}
ctx.setBeanProvider(ctxBean);
final String t = rendererOption.template();
if (t.length() > 0) {
// Assemble all the keys to be used in the template.
List<String> replaceables = new ArrayList<String>();
for (String key : rendererOption.templatekeys()) {
if (t.contains(key)) {
replaceables.add(key);
}
}
for (String key : ctxBean.getChildKeys()) {
if (t.contains("${"+key+"}")) {
replaceables.add("${"+key+"}");
}
if (t.contains("${"+key+".name()}")) {
replaceables.add("${"+key+".name()}");
}
}
ctx.setTemplate(new MappedTemplate(t, replaceables.toArray(new String[replaceables.size()])));
}
}
return ctxes;
}
protected Validator[] getValidators(UiRenderingContext ctx,
UiRendererOptions rendererOptions) {
return null;
}
protected UiRendererSelector getSelector(UiRenderingContext ctx,
UiRendererOptions rendererOptions) {
return create(rendererOptions.selector());
}
protected <X> X create(Class<? extends X> renderer) {
try {
return renderer.newInstance();
} catch (InstantiationException e) {
throw X_Debug.rethrow(e.getCause() == null ? e : e.getCause());
} catch (IllegalAccessException e) {
throw X_Util.rethrow(e);
}
}
@Override
public BeanValueProvider getBeanProvider(Class<?> cls) {
BeanValueProvider bean = new BeanValueProvider();
ConvertsValue<Object, Object> valueConverter = new ConvertsValue<Object, Object>() {
@Override
public Object convert(Object from) {
return from;
}
};
bean.addProvider("this", getName(cls), valueConverter);
// Now, go through all the getter methods, and add bean providers for everything we need
recursiveAddBeanValues(bean, cls, valueConverter, "", 0);
return bean;
}
protected abstract void recursiveAddBeanValues(BeanValueProvider bean, Class<?> cls, ConvertsValue<Object, Object> valueConverter, String prefix, int depth);
protected String getNameFromMethod(Method from) {
if (from.isAnnotationPresent(Named.class)) {
return from.getAnnotation(Named.class).value();
}
String name = from.getName();
if (name.startsWith("get") || name.startsWith("has")) {
if (name.length() > 3 && Character.isUpperCase(name.charAt(3))) {
name = Character.toLowerCase(name.charAt(3)) +
(name.length() > 4 ? name.substring(4) : "");
}
} else if (name.startsWith("is")) {
if (name.length() > 2 && Character.isUpperCase(name.charAt(2))) {
name = Character.toLowerCase(name.charAt(2)) +
(name.length() > 3 ? name.substring(3) : "");
}
}
return name;
}
protected String getName(Class<?> from) {
if (from.isAnnotationPresent(Named.class)) {
return from.getAnnotation(Named.class).value();
}
return from.getName();
}
@SuppressWarnings("unchecked")
private final <T, U extends UserInterface<T>> U instantiateUi(
final Class<? extends T> type,
final Class<? super U> uiType,
final UiRenderingContext[] head) {
final U ui = (U) newUi((Class)type, (Class)uiType);
try {
return ui;
} finally {
ui.setRenderers(head);
}
}
@SuppressWarnings("unchecked")
protected <T, U extends UserInterface<T>> U newUi(Class<? extends T> type, Class<? super U> uiType) {
if (uiType == null) {
throw new NullPointerException("Must specify UI type for "+type);
}
try {
return (U) uiType.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
X_Log.error(getClass(), "Unable to instantiate",uiType, e);
throw X_Debug.rethrow(e);
}
}
}